// 导入Electron主模块
const { app, BrowserWindow, ipcMain, Menu, dialog, shell, globalShortcut } = require('electron');

const { spawn } = require("child_process");
const path = require("path");
const iconv = require("iconv-lite");
const fs = require('fs');

// 全局变量用于存储日志文件路径
let logFilePath = null;

// 初始化日志文件
function initLogFile() {
  try {
    // 获取应用根目录路径 - 在打包环境中这是安装目录
    const appPath = app.isPackaged ? path.dirname(app.getPath('exe')) : __dirname;
    
    // 创建logs目录（如果不存在）
    const logsDir = path.join(appPath, 'logs');
    if (!fs.existsSync(logsDir)) {
      fs.mkdirSync(logsDir, { recursive: true });
    }
    
    // 获取当前日期作为日志文件名的一部分
    const today = new Date();
    const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
    
    // 设置日志文件路径
    logFilePath = path.join(logsDir, `app-${dateStr}.log`);
    
    // 检查是否需要滚动日志文件（日期滚动）
    checkLogFileRollover();
    
    // 写入启动日志
    const timestamp = new Date().toISOString();
    const startupMessage = `[${timestamp}] 应用启动\n`;
    fs.appendFileSync(logFilePath, startupMessage, 'utf8');
    
    console.log('日志文件已初始化: ' + logFilePath);
  } catch (error) {
    console.error('初始化日志文件失败: ' + error.message);
  }
}

// 检查日志文件是否需要滚动
function checkLogFileRollover() {
  try {
    if (!logFilePath || !fs.existsSync(logFilePath)) {
      return;
    }
    
    // 获取当前日期字符串
    const today = new Date();
    const currentDateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
    
    // 从日志文件路径中提取日期
    const fileDateMatch = logFilePath.match(/app-(\d{4}-\d{2}-\d{2})\.log/);
    
    // 日期滚动检查
    if (logConfig.ROLLOVER_TYPE === 'date' || logConfig.ROLLOVER_TYPE === 'both') {
      if (fileDateMatch && fileDateMatch[1] !== currentDateStr) {
        // 日期已变更，需要滚动日志文件
        rolloverLogFile();
        return;
      }
    }
    
    // 文件大小滚动检查
    if (logConfig.ROLLOVER_TYPE === 'size' || logConfig.ROLLOVER_TYPE === 'both') {
      const stats = fs.statSync(logFilePath);
      if (stats.size >= logConfig.MAX_FILE_SIZE) {
        // 文件大小超过限制，需要滚动日志文件
        rolloverLogFile();
        return;
      }
    }
  } catch (error) {
    console.error('检查日志文件滚动失败:', error.message);
  }
}

// 滚动日志文件
function rolloverLogFile() {
  try {
    if (!logFilePath || !fs.existsSync(logFilePath)) {
      return;
    }
    
    // 获取日志文件目录和基本名称
    const logsDir = path.dirname(logFilePath);
    const baseName = path.basename(logFilePath);
    
    // 获取当前日期时间作为滚动标识
    const now = new Date();
    const timestamp = `${now.getHours()}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
    
    // 创建滚动后的日志文件名
    const rolledFileName = baseName.replace('.log', `_${timestamp}.log`);
    const rolledFilePath = path.join(logsDir, rolledFileName);
    
    // 重命名当前日志文件
    fs.renameSync(logFilePath, rolledFilePath);
    console.log(`日志文件已滚动: ${rolledFilePath}`);
    
    // 清理旧日志文件，保留指定数量的备份
    cleanupOldLogFiles();
    
    // 重置日志文件路径，让initLogFile创建新文件
    initLogFile();
  } catch (error) {
    console.error('滚动日志文件失败:', error.message);
  }
}

// 清理旧日志文件
function cleanupOldLogFiles() {
  try {
    const logsDir = path.join(appPath, 'logs');
    if (!fs.existsSync(logsDir)) {
      return;
    }
    
    // 读取日志目录中的所有文件
    const files = fs.readdirSync(logsDir)
      .map(file => path.join(logsDir, file))
      .filter(filePath => {
        // 只考虑.log文件
        return fs.statSync(filePath).isFile() && filePath.endsWith('.log');
      })
      // 按文件修改时间排序（最新的在前）
      .sort((a, b) => fs.statSync(b).mtime.getTime() - fs.statSync(a).mtime.getTime());
    
    // 删除超过最大保留数量的旧文件
    while (files.length > logConfig.MAX_BACKUP_FILES) {
      const oldestFile = files.pop();
      try {
        fs.unlinkSync(oldestFile);
        console.log(`已删除旧日志文件: ${oldestFile}`);
      } catch (err) {
        console.error(`删除旧日志文件失败: ${oldestFile}`, err.message);
      }
    }
  } catch (error) {
    console.error('清理旧日志文件失败:', error.message);
  }
}

// 自定义JSON序列化函数，处理BigInt类型
function safeJsonStringify(value) {
  try {
    return JSON.stringify(value, (key, value) => {
      // 处理BigInt类型
      if (typeof value === 'bigint') {
        return value.toString();
      }
      // 处理Buffer类型
      if (Buffer.isBuffer(value)) {
        return `Buffer(${value.length} bytes)`;
      }
      return value;
    }, 2);
  } catch (e) {
    // 如果序列化失败，返回基本信息
    return `[序列化错误: ${e.message}] 值类型: ${typeof value}`;
  }
}

// 统一日志输出函数 - 同时输出到控制台、日志文件和渲染端
function unifiedLog(message, level = 'info') {
  try {
    // 格式化消息（如果是对象则转为字符串）
    const formattedMessage = typeof message === 'object' ? safeJsonStringify(message) : String(message);
    
    // 1. 输出到控制台
    switch(level) {
      case 'error':
        console.error(formattedMessage);
        break;
      case 'warn':
        console.warn(formattedMessage);
        break;
      case 'debug':
        if (logConfig.CONSOLE_DEBUG || process.env.DEBUG) {
          console.debug(formattedMessage);
        }
        break;
      default:
        console.log(formattedMessage);
    }
    
    // 2. 写入日志文件（只写入达到级别要求的日志）
    if (shouldWriteToFile(level)) {
      const timestamp = new Date().toISOString();
      const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${formattedMessage}`;
      
      try {
        // 如果日志文件路径未初始化，先初始化
        if (!logFilePath) {
          initLogFile();
          // 如果初始化后仍未获取到日志文件路径，放弃写入
          if (!logFilePath) return;
        }
        
        // 检查是否需要滚动日志文件
        checkLogFileRollover();
        
        // 写入日志文件
        fs.appendFileSync(logFilePath, logMessage + '\n', 'utf8');
      } catch (logError) {
        // 如果写入日志文件失败，至少在控制台显示错误
        console.error('写入日志文件失败:', logError.message);
      }
    }
    
    // 3. 发送到渲染端
    if (mainWindow && !mainWindow.isDestroyed()) {
      mainWindow.webContents.send('log-message', {
        timestamp: new Date().toISOString(),
        level,
        message: formattedMessage
      });
    }
  } catch (error) {
    // 确保即使出现异常也不会导致应用崩溃
    console.error('统一日志函数执行失败:', error);
  }
}

// 写入日志到文件的函数
function logToFile(message) {
  try {
    // 默认为info级别
    if (!shouldWriteToFile('info')) {
      return;
    }
    
    // 如果日志文件路径未初始化，先初始化
    if (!logFilePath) {
      initLogFile();
      // 如果初始化后仍未获取到日志文件路径，放弃写入
      if (!logFilePath) return;
    }
    
    // 检查是否需要滚动日志文件
    checkLogFileRollover();
    
    // 格式化日志消息，添加时间戳
    const timestamp = new Date().toISOString();
    const logMessage = `[${timestamp}] ${message}\n`;
    
    // 写入日志到文件
    fs.appendFileSync(logFilePath, logMessage, 'utf8');
  } catch (error) {
    // 如果写入日志文件失败，至少在控制台显示错误
    console.error('写入日志文件失败:', error.message);
  }
}

// 写入错误日志到文件的函数
function logErrorToFile(message, error) {
  try {
    let errorMessage;
    try {
      // 安全地处理错误对象，避免BigInt序列化问题
      if (error instanceof Error) {
        errorMessage = `${message}: ${error.message}`;
        // 添加堆栈信息，但避免序列化错误
        if (error.stack) {
          errorMessage += `\n${error.stack}`;
        }
      } else if (typeof error === 'object' && error !== null) {
        // 如果是对象，使用安全的序列化
        errorMessage = `${message}: ${safeJsonStringify(error)}`;
      } else {
        errorMessage = `${message}: ${String(error)}`;
      }
    } catch (e) {
      // 如果处理错误信息本身也出错，使用最基本的信息
      errorMessage = `${message}: [无法解析的错误对象]`;
    }
    
    // 先在控制台显示错误
    console.error(errorMessage);
    
    // 错误日志总是写入，不受日志级别限制
    try {
      // 如果日志文件路径未初始化，先初始化
      if (!logFilePath) {
        initLogFile();
        // 如果初始化后仍未获取到日志文件路径，放弃写入
        if (!logFilePath) return;
      }
      
      // 检查是否需要滚动日志文件
      checkLogFileRollover();
      
      // 格式化日志消息，添加时间戳
      const timestamp = new Date().toISOString();
      const logMessage = `[${timestamp}] [ERROR] ${errorMessage}\n`;
      
      // 写入日志到文件
      fs.appendFileSync(logFilePath, logMessage, 'utf8');
    } catch (logError) {
      // 如果写入日志文件失败，至少在控制台显示原始错误
      console.error('写入错误日志文件失败:', logError.message);
    }
  } catch (logError) {
    // 如果写入错误日志文件失败，至少在控制台显示原始错误
    console.error('写入错误日志文件失败:', logError.message);
  }
}

// 全局声明mainWindow变量
let mainWindow = null;

// 添加RTSP流服务器功能
let rtspServerProcess = null;
let websocketServer = null;
const https = require('https'); // ✅ HTTPS请求模块
const http = require('http');
const axios = require("axios"); // ✅ HTTP请求模块

// 获取应用根目录路径 - 在打包环境中这是安装目录
const appPath = app.isPackaged ? path.dirname(app.getPath('exe')) : __dirname;

// 构建配置文件的绝对路径 - 首先尝试从resources目录读取
// 注意：打包后配置文件位于exe所在目录的resources文件夹中
let electronConfig;

// 主要路径 - 直接从resources目录读取（打包环境下）
if (app.isPackaged) {
  try {
    const resourcesConfigPath = path.join(appPath, 'resources', 'electron-config.js');
    electronConfig = require(resourcesConfigPath);
    console.log('成功从resources目录加载electron-config.js配置文件:', resourcesConfigPath);
  } catch (resourcesError) {
    logErrorToFile('无法从resources目录加载electron-config.js', resourcesError);
    // 继续尝试其他路径
  }
}

// 如果resources路径加载失败或不是打包环境，尝试应用程序根目录
if (!electronConfig) {
  try {
    const electronConfigPath = path.join(appPath, 'electron-config.js');
    electronConfig = require(electronConfigPath);
    console.log('成功加载electron-config.js配置文件（应用程序根目录）:', electronConfigPath);
  } catch (error) {
    logErrorToFile('无法加载electron-config.js（应用程序根目录）', error);
    // 继续尝试开发环境路径
  }
}

// 如果前面的路径都加载失败，尝试开发环境路径
if (!electronConfig) {
  try {
    const devConfigPath = path.join(__dirname, 'electron-config.js');
    electronConfig = require(devConfigPath);
    console.log('成功使用开发环境路径加载配置:', devConfigPath);
  } catch (devError) {
    logErrorToFile('开发环境路径也加载失败', devError);
    // 创建默认配置作为最后的备用方案
    electronConfig = {
      getBaseUrl: () => 'http://localhost:5174/api',
      APP_CONFIG: {
        DATA_PROCESSING: {
          RENDER_BATCH_SIZE: 8,
          RENDER_BATCH_INTERVAL: 80000,
          DB_BATCH_SIZE: 8,
          DB_BATCH_INTERVAL: 80000,
          FILE_BATCH_SIZE: 10,
          FILE_BATCH_INTERVAL: 80000
        },
        // 默认日志配置
        LOGGING: {
          MAX_FILE_SIZE: 200 * 1024 * 1024, // 10MB
          MAX_BACKUP_FILES: 10,
          ROLLOVER_TYPE: 'both',
          FILE_LOG_LEVEL: 'info',
          CONSOLE_DEBUG: false
        }
      }
    };
    console.log('使用默认配置');
    
    // 写入日志文件
    logToFile('使用默认配置');
  }
}

// 获取日志配置，如果不存在则使用默认值
const logConfig = electronConfig.APP_CONFIG?.LOGGING || {
  MAX_FILE_SIZE: 200 * 1024 * 1024, // 10MB
  MAX_BACKUP_FILES: 10,
  ROLLOVER_TYPE: 'both',
  FILE_LOG_LEVEL: 'info',
  CONSOLE_DEBUG: false
};

// 日志级别优先级映射
const LOG_LEVEL_PRIORITY = {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3
};

// 检查是否应该写入日志文件
function shouldWriteToFile(level) {
  return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[logConfig.FILE_LOG_LEVEL];
}

// 设置axios基础URL为配置文件中定义的地址
axios.defaults.baseURL = electronConfig.getBaseUrl();
console.log('Electron主进程配置的baseURL:', axios.defaults.baseURL);

// 重新加载配置的函数
function reloadConfig() {
  try {
    // 尝试重新加载配置文件，使用与主程序相同的健壮加载逻辑
    let newConfigData = null;
    
    // 1. 首先尝试从resources目录重新加载（打包环境下）
    if (app.isPackaged) {
      try {
        const resourcesConfigPath = path.join(appPath, 'resources', 'electron-config.js');
        if (fs.existsSync(resourcesConfigPath) && require.cache[require.resolve(resourcesConfigPath)]) {
          delete require.cache[require.resolve(resourcesConfigPath)];
        }
        newConfigData = require(resourcesConfigPath);
        console.log('成功从resources目录重新加载配置:', resourcesConfigPath);
      } catch (resourcesError) {
        logErrorToFile('无法从resources目录重新加载配置', resourcesError);
        // 继续尝试其他路径
      }
    }
    
    // 2. 如果resources路径加载失败或不是打包环境，尝试应用程序根目录
    if (!newConfigData) {
      try {
        const rootConfigPath = path.join(appPath, 'electron-config.js');
        if (fs.existsSync(rootConfigPath) && require.cache[require.resolve(rootConfigPath)]) {
          delete require.cache[require.resolve(rootConfigPath)];
        }
        newConfigData = require(rootConfigPath);
        console.log('成功从应用程序根目录重新加载配置:', rootConfigPath);
      } catch (error) {
        logErrorToFile('无法从应用程序根目录重新加载配置', error);
        // 继续尝试开发环境路径
      }
    }
    
    // 3. 如果前面的路径都加载失败，尝试开发环境路径
    if (!newConfigData) {
      try {
        const devConfigPath = path.join(__dirname, 'electron-config.js');
        if (fs.existsSync(devConfigPath) && require.cache[require.resolve(devConfigPath)]) {
          delete require.cache[require.resolve(devConfigPath)];
        }
        newConfigData = require(devConfigPath);
        console.log('成功从开发环境路径重新加载配置:', devConfigPath);
      } catch (altError) {
        logErrorToFile('备用路径也重新加载失败', altError);
        // 保持原有配置不变
        console.log('保持原有配置不变');
        return;
      }
    }
    
    // 更新全局配置对象
    electronConfig = newConfigData;
    
    // 更新axios的baseURL
    axios.defaults.baseURL = electronConfig.getBaseUrl();
    console.log('配置文件已更新，新的baseURL:', axios.defaults.baseURL);
    
    // 重新设置数据处理配置
    if (typeof DATA_PROCESSING_CONFIG !== 'undefined') {
      const newConfig = electronConfig.APP_CONFIG.DATA_PROCESSING;
      if (typeof RENDER_BATCH_SIZE !== 'undefined') RENDER_BATCH_SIZE = newConfig.RENDER_BATCH_SIZE;
      if (typeof DB_BATCH_SIZE !== 'undefined') DB_BATCH_SIZE = newConfig.DB_BATCH_SIZE;
      if (typeof FILE_BATCH_SIZE !== 'undefined') FILE_BATCH_SIZE = newConfig.FILE_BATCH_SIZE;
      if (typeof RENDER_BATCH_INTERVAL !== 'undefined') RENDER_BATCH_INTERVAL = newConfig.RENDER_BATCH_INTERVAL;
      if (typeof DB_BATCH_INTERVAL !== 'undefined') DB_BATCH_INTERVAL = newConfig.DB_BATCH_INTERVAL;
      if (typeof FILE_BATCH_INTERVAL !== 'undefined') FILE_BATCH_INTERVAL = newConfig.FILE_BATCH_INTERVAL;
      
      unifiedLog('数据处理配置已更新: ' + JSON.stringify({
        RENDER_BATCH_SIZE,
        DB_BATCH_SIZE,
        FILE_BATCH_SIZE,
        RENDER_BATCH_INTERVAL,
        DB_BATCH_INTERVAL,
        FILE_BATCH_INTERVAL
      }));
      
      // 通知渲染进程配置已更新
      if (mainWindow && !mainWindow.isDestroyed()) {
        mainWindow.webContents.send('config-updated', {
          dataProcessing: newConfig
        });
      }
    }
  } catch (error) {
    logErrorToFile('重新加载配置文件失败', error);
  }
}

// 监视配置文件变化，使其修改后立即生效 - 按照加载顺序尝试监视存在的配置文件
function setupConfigWatcher() {
  // 首先尝试监视resources目录下的配置文件（打包环境）
  if (app.isPackaged) {
    const resourcesConfigPath = path.join(appPath, 'resources', 'electron-config.js');
    if (fs.existsSync(resourcesConfigPath)) {
      fs.watchFile(resourcesConfigPath, {
        interval: 1000 // 每秒检查一次变化
      }, (curr, prev) => {
        if (curr.mtime !== prev.mtime) {
          unifiedLog('检测到resources目录配置文件变化，重新加载配置...');
          reloadConfig();
        }
      });
      unifiedLog('已开始监视resources目录配置文件: ' + resourcesConfigPath);
      return;
    }
  }
  
  // 然后尝试监视应用程序根目录的配置文件
  const rootConfigPath = path.join(appPath, 'electron-config.js');
  if (fs.existsSync(rootConfigPath)) {
    fs.watchFile(rootConfigPath, {
      interval: 1000 // 每秒检查一次变化
    }, (curr, prev) => {
      if (curr.mtime !== prev.mtime) {
        unifiedLog('检测到应用程序根目录配置文件变化，重新加载配置...');
        reloadConfig();
      }
    });
    unifiedLog('已开始监视应用程序根目录配置文件: ' + rootConfigPath);
    return;
  }
  
  // 最后尝试监视开发环境路径的配置文件
  const devConfigPath = path.join(__dirname, 'electron-config.js');
  if (fs.existsSync(devConfigPath)) {
    fs.watchFile(devConfigPath, {
      interval: 1000 // 每秒检查一次变化
    }, (curr, prev) => {
      if (curr.mtime !== prev.mtime) {
        unifiedLog('检测到开发环境配置文件变化，重新加载配置...');
        reloadConfig();
      }
    });
    unifiedLog('已开始监视开发环境配置文件: ' + devConfigPath);
  } else {
    unifiedLog('没有找到可监视的配置文件，使用默认配置', 'warn');
  }
}

// 设置配置文件监视
setupConfigWatcher();

// 在应用退出时取消所有配置文件监视
process.on('exit', () => {
  // 取消监视resources目录配置文件
  if (app.isPackaged) {
    const resourcesConfigPath = path.join(appPath, 'resources', 'electron-config.js');
    fs.unwatchFile(resourcesConfigPath);
  }
  
  // 取消监视应用程序根目录配置文件
  const rootConfigPath = path.join(appPath, 'electron-config.js');
  fs.unwatchFile(rootConfigPath);
  
  // 取消监视开发环境配置文件
  const devConfigPath = path.join(__dirname, 'electron-config.js');
  fs.unwatchFile(devConfigPath);
});
//import {taskTypeAPI} from "../api";
// UDP 相关
const dgram = require('dgram'); // 在主进程中正确引入 dgram
// 添加 UDP 相关变量
let udpSocket = null;

// TCP 相关
// 使用绝对路径导入tcpClient模块，确保在打包后能正确找到
const TcpConfigDownloader = require('../src/api/modules/tcpClient');



// 启动RTSP流服务器
// function startRTSPServer() {
//   if (rtspServerProcess) {
//     console.log('RTSP服务器已在运行');
//     return;
//   }

//   try {
//     console.log('启动RTSP流服务器...');

//     // 获取应用根目录
//     const appPath = app.isPackaged ? path.dirname(app.getPath('exe')) : __dirname;
//     const serverScriptPath = path.join(appPath, 'resources', 'app', 'scripts', 'rtsp-universal-server.js');

//     // 如果打包后找不到脚本，使用内置的服务器逻辑
//     if (!require('fs').existsSync(serverScriptPath)) {
//       console.log('使用内置RTSP服务器...');
//       startBuiltinRTSPServer();
//       return;
//     }

//     // 启动外部服务器脚本
//     rtspServerProcess = spawn('node', [serverScriptPath], {
//       stdio: ['pipe', 'pipe', 'pipe'],
//       cwd: path.dirname(serverScriptPath)
//     });

//     rtspServerProcess.stdout.on('data', (data) => {
//       console.log('RTSP服务器输出:', data.toString());
//     });

//     rtspServerProcess.stderr.on('data', (data) => {
//       console.log('RTSP服务器错误:', data.toString());
//     });

//     rtspServerProcess.on('close', (code) => {
//       console.log(`RTSP服务器进程退出，代码: ${code}`);
//       rtspServerProcess = null;
//     });

//     rtspServerProcess.on('error', (error) => {
//       console.error('RTSP服务器启动失败:', error);
//       rtspServerProcess = null;
//     });

//   } catch (error) {
//     console.error('启动RTSP服务器时出错:', error);
//     // 如果外部服务器启动失败，使用内置服务器
//     startBuiltinRTSPServer();
//   }
// }

// 内置RTSP服务器
// function startBuiltinRTSPServer() {
//   const http = require('http');
//   const url = require('url');

//   const server = http.createServer((req, res) => {
//     // 设置CORS头
//     res.setHeader('Access-Control-Allow-Origin', '*');
//     res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
//     res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

//     if (req.method === 'OPTIONS') {
//       res.writeHead(200);
//       res.end();
//       return;
//     }

//     // 解析URL参数
//     const parsedUrl = url.parse(req.url, true);
//     const rtspUrl = parsedUrl.query.rtsp;
//     const format = parsedUrl.query.format || 'mp4';
//     const transport = parsedUrl.query.transport || 'tcp';

//     if (!rtspUrl) {
//       res.writeHead(400, { 'Content-Type': 'text/plain' });
//       res.end('缺少RTSP URL参数');
//       return;
//     }

//     console.log(`处理RTSP流: ${rtspUrl}, 格式: ${format}, 传输: ${transport}`);

//     // 设置响应头
//     const contentType = format === 'mp4' ? 'video/mp4' :
//                        format === 'flv' ? 'video/x-flv' :
//                        'video/mp2t';

//     res.writeHead(200, {
//       'Content-Type': contentType,
//       'Access-Control-Allow-Origin': '*',
//       'Cache-Control': 'no-cache',
//       'Connection': 'keep-alive'
//     });

//     try {
//       // FFmpeg参数
//       let ffmpegArgs = [
//         '-rtsp_transport', transport,
//         '-i', rtspUrl,
//         '-c:v', 'libx264',
//         '-c:a', 'aac',
//         '-preset', 'ultrafast',
//         '-tune', 'zerolatency',
//         '-analyzeduration', '2000000',
//         '-probesize', '2000000',
//         '-fflags', '+genpts+igndts',
//         '-avoid_negative_ts', 'make_zero',
//         '-b:v', '800k',
//         '-b:a', '128k',
//         '-r', '25',
//         '-s', '1280x720',
//         '-g', '25',
//         '-keyint_min', '25',
//         '-sc_threshold', '0',
//         '-bf', '0',
//         '-refs', '1',
//         '-profile:v', 'baseline',
//         '-level', '3.1',
//         '-pix_fmt', 'yuv420p',
//         '-x264opts', 'no-scenecut'
//       ];

//       // 根据格式添加特定参数
//       if (format === 'mp4') {
//         ffmpegArgs = ffmpegArgs.concat([
//           '-f', 'mp4',
//           '-movflags', '+frag_keyframe+empty_moov+faststart',
//           '-frag_duration', '1000000',
//           '-max_muxing_queue_size', '1024'
//         ]);
//       } else if (format === 'flv') {
//         ffmpegArgs = ffmpegArgs.concat([
//           '-f', 'flv',
//           '-flvflags', 'no_duration_filesize'
//         ]);
//       } else if (format === 'mpegts') {
//         ffmpegArgs = ffmpegArgs.concat([
//           '-f', 'mpegts',
//           '-mpegts_flags', '+initial_discontinuity'
//         ]);
//       }

//       ffmpegArgs.push('pipe:1');

//       console.log('启动FFmpeg进程:', ffmpegArgs.join(' '));

//       const ffmpegProcess = spawn('ffmpeg', ffmpegArgs, {
//         stdio: ['ignore', 'pipe', 'pipe']
//       });

//       // 处理FFmpeg输出
//       ffmpegProcess.stdout.on('data', (data) => {
//         res.write(data);
//       });

//       // 处理FFmpeg错误输出
//       ffmpegProcess.stderr.on('data', (data) => {
//         console.log('FFmpeg stderr:', data.toString());
//       });

//       // 处理FFmpeg进程结束
//       ffmpegProcess.on('close', (code) => {
//         console.log(`FFmpeg进程结束，退出码: ${code}`);
//         res.end();
//       });

//       // 处理FFmpeg进程错误
//       ffmpegProcess.on('error', (error) => {
//         console.log('FFmpeg进程错误:', error);
//         res.writeHead(500, { 'Content-Type': 'text/plain' });
//         res.end(`FFmpeg进程错误: ${error.message}`);
//       });

//       // 处理客户端断开连接
//       req.on('close', () => {
//         console.log('客户端断开连接');
//         ffmpegProcess.kill('SIGTERM');
//       });

//     } catch (error) {
//       console.log('启动FFmpeg失败:', error);
//       res.writeHead(500, { 'Content-Type': 'text/plain' });
//       res.end(`启动FFmpeg失败: ${error.message}`);
//     }
//   });

//   const PORT = 8080;
//   server.listen(PORT, () => {
//     console.log(`内置RTSP流服务器已启动，监听端口 ${PORT}`);
//   });
// }

// 启动WebSocket FLV服务器
function startWebSocketServer() {
  if (websocketServer) {
    unifiedLog('WebSocket服务器已在运行');
    return;
  }

  try {
    unifiedLog('启动WebSocket FLV服务器...');

    // 获取应用根目录
    const appPath = app.isPackaged ? path.dirname(app.getPath('exe')) : __dirname;
    const serverScriptPath = path.join(appPath, 'resources', 'app', 'scripts', 'rtsp-websocket-flv-server-improved.js');

    // 如果打包后找不到脚本，使用内置的WebSocket服务器逻辑
    if (!require('fs').existsSync(serverScriptPath)) {
      unifiedLog('使用内置WebSocket服务器...');
      startBuiltinWebSocketServer();
      return;
    }

    // 启动外部服务器脚本
    websocketServer = spawn('node', [serverScriptPath], {
      stdio: ['pipe', 'pipe', 'pipe'],
      cwd: path.dirname(serverScriptPath)
    });

    websocketServer.stdout.on('data', (data) => {
      unifiedLog('WebSocket服务器输出: ' + data.toString());
    });

    websocketServer.stderr.on('data', (data) => {
      unifiedLog('WebSocket服务器错误: ' + data.toString());
    });

    websocketServer.on('close', (code) => {
      unifiedLog(`WebSocket服务器进程退出，代码: ${code}`);
      websocketServer = null;
    });

    websocketServer.on('error', (error) => {
      unifiedLog('WebSocket服务器启动失败: ' + error, 'error');
      websocketServer = null;
    });

  } catch (error) {
    unifiedLog('启动WebSocket服务器时出错: ' + error, 'error');
    // 如果外部服务器启动失败，使用内置服务器
    startBuiltinWebSocketServer();
  }
}

// 内置WebSocket服务器
function startBuiltinWebSocketServer() {
  const WebSocket = require('ws');
  const webSocketStream = require('websocket-stream/stream');
  const ffmpeg = require('fluent-ffmpeg');
  const os = require('os');

  const isWindows7 = () => {
    if (process.platform !== 'win32') return false;
    // os.release() on Win7 typically starts with 6.1
    try { return /^6\.1(\.|$)/.test(os.release()); } catch { return false; }
  };

  // 建立WebSocket服务
  websocketServer = new WebSocket.Server({ port: 9527, perMessageDeflate: false });

  unifiedLog('内置WebSocket FLV服务器已启动，监听端口 9527');
  unifiedLog('连接格式: ws://localhost:9527/rtsp://your-rtsp-url');

  // 监听连接
  websocketServer.on('connection', handleWebSocketConnection);

  // 连接时触发事件
  function handleWebSocketConnection(ws, req) {
    unifiedLog('新的WebSocket连接: ' + req.url);

    // 获取前端请求的流地址
    const url = req.url.slice(1);

    if (!url || !url.startsWith('rtsp://')) {
      unifiedLog('缺少有效的RTSP URL参数: ' + url);
      ws.close(1000, '缺少有效的RTSP URL参数');
      return;
    }

    unifiedLog('处理RTSP流: ' + url);

    // 传入连接的ws客户端 实例化一个流
    const stream = webSocketStream(ws, { binary: true });

    const useWin7Path = isWindows7();
    unifiedLog('转码策略选择: ' + (useWin7Path ? 'Windows 7 兼容路径(探测+必要转码)' : '非 Win7 简化路径(尽量直拷)'));

    // 临时要求：统一使用直通测试，暂停 Win7 特殊配置
    // 注释掉下面一行即可恢复 Win7 专用路径
    const forcePassthroughForAll = true;
    if (forcePassthroughForAll) {
      const ffmpegCommand = ffmpeg(url)
        .addInputOption('-rtsp_transport', 'tcp')
        .addInputOption('-rtsp_flags', 'prefer_tcp')
        .addInputOption('-analyzeduration', '2000000')
        .addInputOption('-probesize', '2000000')
        .addInputOption('-max_delay', '2000000')
        .addInputOption('-fflags', '+genpts+igndts')
        .addInputOption('-avoid_negative_ts', 'make_zero')
        .addInputOption('-timeout', '5000000')
        .on('start', function () { unifiedLog('Stream started for: ' + url) })
        .on('codecData', function () { unifiedLog('Stream codecData for: ' + url) })
        .on('error', function (err) { unifiedLog('An error occured for ' + url + ': ' + err.message); stream.end(); })
        .on('end', function () { unifiedLog('Stream end for: ' + url); stream.end(); })
        .outputFormat('flv')
        .videoCodec('copy')
        .audioCodec('copy')
        .outputOptions(['-flvflags','no_duration_filesize']);

      stream.on('close', function () {
        unifiedLog('Stream closed for: ' + url);
        try { ffmpegCommand.kill('SIGKILL'); } catch (e) { unifiedLog('Failed to kill FFmpeg process: ' + e.message); }
      });

      stream.on('error', function (error) {
        unifiedLog('Stream error for: ' + url + ' ' + error.message);
        try { ffmpegCommand.kill('SIGKILL'); } catch (e) { unifiedLog('Failed to kill FFmpeg process on stream error: ' + e.message); }
      });

      try {
        ffmpegCommand.pipe(stream);
        unifiedLog('FFmpeg command piped to stream for: ' + url + ' (copy passthrough, forced)');
      } catch (error) {
        unifiedLog('FFmpeg启动失败: ' + error, 'error');
        stream.end();
      }
      return;
    }

    if (!useWin7Path) {
      // 非 Win7：回到原来的"尽量直拷"配置，避免不必要转码引发的不兼容
      const ffmpegCommand = ffmpeg(url)
        .addInputOption('-rtsp_transport', 'tcp')
        .addInputOption('-rtsp_flags', 'prefer_tcp')
        .addInputOption('-analyzeduration', '2000000')
        .addInputOption('-probesize', '2000000')
        .addInputOption('-max_delay', '2000000')
        .addInputOption('-fflags', '+genpts+igndts')
        .addInputOption('-avoid_negative_ts', 'make_zero')
        .addInputOption('-timeout', '5000000')
        .on('start', function () { unifiedLog('Stream started for: ' + url) })
        .on('codecData', function () { unifiedLog('Stream codecData for: ' + url) })
        .on('error', function (err) { unifiedLog('An error occured for ' + url + ': ' + err.message); stream.end(); })
        .on('end', function () { unifiedLog('Stream end for: ' + url); stream.end(); })
        .outputFormat('flv')
        .videoCodec('copy')
        .audioCodec('copy')
        .outputOptions(['-flvflags','no_duration_filesize']);

      stream.on('close', function () {
        unifiedLog('Stream closed for: ' + url);
        try { ffmpegCommand.kill('SIGKILL'); } catch (e) { unifiedLog('Failed to kill FFmpeg process: ' + e.message); }
      });

      stream.on('error', function (error) {
        unifiedLog('Stream error for: ' + url + ' ' + error.message);
        try { ffmpegCommand.kill('SIGKILL'); } catch (e) { unifiedLog('Failed to kill FFmpeg process on stream error: ' + e.message); }
      });

      try {
        ffmpegCommand.pipe(stream);
        unifiedLog('FFmpeg command piped to stream for: ' + url + ' (copy passthrough)');
      } catch (error) {
        unifiedLog('FFmpeg启动失败: ' + error, 'error');
        stream.end();
      }
      return;
    }

    // Win7：使用探测+必要转码到 H.264/AAC 720p 的逻辑（已在上方被强制直通临时关闭）
    // 先探测源流编解码与分辨率，尽量直拷；否则转码到 H.264/AAC 720p
    const startWithInfo = (probe) => {
      const videoCodec = probe?.videoCodec || 'unknown';
      const audioCodec = probe?.audioCodec || 'unknown';
      const width = probe?.width || 0;
      const height = probe?.height || 0;
      const canCopy = (videoCodec === 'h264') && (audioCodec === 'aac') && (width <= 1280) && (height <= 720);
      unifiedLog('================ RTSP 源流探测结果 ================');
      unifiedLog('URL: ' + url);
      unifiedLog('视频编码: ' + videoCodec + ' 音频编码: ' + audioCodec);
      unifiedLog('分辨率: ' + width + 'x' + height);
      unifiedLog('策略: ' + (canCopy ? '直拷 (copy)' : '转码 (transcode 至 H.264/AAC 720p)'));
      unifiedLog('====================================================');

      const cmd = ffmpeg(url)
        .addInputOption('-rtsp_transport', 'tcp')
        .addInputOption('-rtsp_flags', 'prefer_tcp')
        .addInputOption('-analyzeduration', '2000000')
        .addInputOption('-probesize', '2000000')
        .addInputOption('-max_delay', '2000000')
        .addInputOption('-fflags', '+genpts+igndts')
        .addInputOption('-avoid_negative_ts', 'make_zero')
        .addInputOption('-timeout', '5000000')
        .on('start', function () { unifiedLog('Stream started for: ' + url) })
        .on('codecData', function () { unifiedLog('Stream codecData for: ' + url) })
        .on('error', function (err) { unifiedLog('An error occured for ' + url + ': ' + err.message); stream.end(); })
        .on('end', function () { unifiedLog('Stream end for: ' + url); stream.end(); })
        .outputFormat('flv');

      if (canCopy) {
        cmd.videoCodec('copy').audioCodec('copy')
           .outputOptions(['-flvflags','no_duration_filesize']);
      } else {
        cmd.videoCodec('libx264').audioCodec('aac')
           .outputOptions([
             '-preset','ultrafast','-tune','zerolatency',
             '-profile:v','baseline','-level','3.1','-pix_fmt','yuv420p',
             '-g','25','-keyint_min','25','-sc_threshold','0','-bf','0','-refs','1',
             '-r','25','-b:v','1500k','-maxrate','2000k','-bufsize','3000k',
             '-vf', 'scale=w=1280:h=720:force_original_aspect_ratio=decrease',
             '-flvflags','no_duration_filesize'
           ])
           .audioChannels(2).audioFrequency(44100).audioBitrate('128k');
      }

      stream.on('close', function () {
        unifiedLog('Stream closed for: ' + url);
        try { cmd.kill('SIGKILL'); } catch (e) { unifiedLog('Failed to kill FFmpeg process: ' + e.message); }
      });

      stream.on('error', function (error) {
        unifiedLog('Stream error for: ' + url + ' ' + error.message);
        try { cmd.kill('SIGKILL'); } catch (e) { unifiedLog('Failed to kill FFmpeg process on stream error: ' + e.message); }
      });

      try {
        cmd.pipe(stream);
        unifiedLog('FFmpeg command piped to stream for: ' + url + ' ' + (canCopy ? '(copy)' : '(transcode)'));
      } catch (error) {
        unifiedLog('FFmpeg启动失败: ' + error, 'error');
        stream.end();
      }
    };

    // 使用 ffprobe 获取编解码信息，失败则直接转码
    try {
      ffmpeg.ffprobe(url, (err, metadata) => {
        if (err || !metadata) {
          unifiedLog('ffprobe 失败，使用转码模式: ' + err?.message);
          startWithInfo(null);
          return;
        }
        const videoStream = (metadata.streams || []).find(s => s.codec_type === 'video');
        const audioStream = (metadata.streams || []).find(s => s.codec_type === 'audio');
        startWithInfo({
          videoCodec: videoStream?.codec_name,
          audioCodec: audioStream?.codec_name,
          width: videoStream?.width,
          height: videoStream?.height
        });
      });
    } catch (e) {
      unifiedLog('ffprobe 异常，使用转码模式: ' + e.message);
      startWithInfo(null);
    }
  }
}

// 停止RTSP流服务器
function stopRTSPServer() {
  if (rtspServerProcess) {
    unifiedLog('停止RTSP流服务器...');
    if (rtspServerProcess.kill) {
      rtspServerProcess.kill('SIGTERM');
    } else if (rtspServerProcess.close) {
      rtspServerProcess.close();
    }
    rtspServerProcess = null;
  }
}

// 停止WebSocket服务器
function stopWebSocketServer() {
  if (websocketServer) {
    unifiedLog('停止WebSocket服务器...');
    if (websocketServer.kill) {
      websocketServer.kill('SIGTERM');
    } else if (websocketServer.close) {
      websocketServer.close();
    }
    websocketServer = null;
  }
}

// 添加执行本地命令的 IPC 处理器
ipcMain.handle("execute-command", async (event, command, args = []) => {
  return new Promise((resolve, reject) => {
    const process = spawn(command, args, {
      shell: true,
      stdio: ["pipe", "pipe", "pipe"],
    });

    let stdout = Buffer.alloc(0);
    let stderr = Buffer.alloc(0);

    process.stdout.on("data", (data) => {
      stdout = Buffer.concat([stdout, data]);
    });

    process.stderr.on("data", (data) => {
      stderr = Buffer.concat([stderr, data]);
    });

    process.on("close", (code) => {
      // 尝试多种编码格式
      let stdoutText, stderrText;

      if (process.platform === "win32") {
        // Windows 尝试多种编码
        try {
          stdoutText = iconv.decode(stdout, "cp936"); // 简体中文
        } catch (e) {
          try {
            stdoutText = iconv.decode(stdout, "gbk");
          } catch (e2) {
            stdoutText = stdout.toString("utf8");
          }
        }

        try {
          stderrText = iconv.decode(stderr, "cp936");
        } catch (e) {
          try {
            stderrText = iconv.decode(stderr, "gbk");
          } catch (e2) {
            stderrText = stderr.toString("utf8");
          }
        }
      } else {
        stdoutText = iconv.decode(stdout, "utf8");
        stderrText = iconv.decode(stderr, "utf8");
      }

      resolve({
        code,
        stdout: stdoutText,
        stderr: stderrText,
        success: code === 0,
      });
    });

    process.on("error", (error) => {
      reject(error);
    });
  });
});

// 添加RTSP服务器控制IPC处理器
// ipcMain.handle("start-rtsp-server", async () => {
//   try {
//     startRTSPServer();
//     return { success: true, message: 'RTSP服务器已启动' };
//   } catch (error) {
//     return { success: false, message: error.message };
//   }
// });

ipcMain.handle("stop-rtsp-server", async () => {
  try {
    stopRTSPServer();
    return { success: true, message: 'RTSP服务器已停止' };
  } catch (error) {
    return { success: false, message: error.message };
  }
});

// 添加WebSocket服务器控制IPC处理器
ipcMain.handle("start-websocket-server", async () => {
  try {
    startWebSocketServer();
    return { success: true, message: 'WebSocket服务器已启动' };
  } catch (error) {
    return { success: false, message: error.message };
  }
});

ipcMain.handle("stop-websocket-server", async () => {
  try {
    stopWebSocketServer();
    return { success: true, message: 'WebSocket服务器已停止' };
  } catch (error) {
    return { success: false, message: error.message };
  }
});

ipcMain.handle("get-rtsp-server-status", async () => {
  return {
    running: rtspServerProcess !== null,
    port: 8080
  };
});

// 添加保存遥测数据的IPC处理器
ipcMain.handle("save-telemetry-data", async (event, options) => {
  const { outputDir, fileName, data } = options;
  const fs = require("fs");
  const path = require("path");

  try {
    // 确保传递的数据是可序列化的
    const serializableData = JSON.parse(JSON.stringify(data));

    // 确保输出目录存在
    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true });
    }

    // 生成遥测数据文件的完整路径
    const filePath = path.join(outputDir, fileName);

    // 将遥测数据写入JSON文件
    fs.writeFileSync(filePath, JSON.stringify(serializableData, null, 2));

    unifiedLog(`遥测数据已保存到: ${filePath}`);
    return {
      success: true,
      filePath: filePath,
    };
  } catch (error) {
    unifiedLog("保存遥测数据失败: " + error, 'error');
    return {
      success: false,
      error: error.message,
    };
  }
});

let activeCollectionProcesses = [];

// 获取FFmpeg路径
function getFFmpegPath() {
  const fs = require('fs');

  // 开发环境下的路径
  const devPath = path.join(__dirname, '..', 'binaries');

  // 生产环境下的路径（打包后）
  const prodPath = process.platform === 'win32'
    ? path.join(process.resourcesPath, 'binaries')
    : path.join(process.resourcesPath, 'binaries');

  // 尝试多个可能的路径
  const possiblePaths = [
    devPath,
    prodPath,
    path.join(app.getAppPath(), 'binaries'),
    path.join(__dirname, '..', '..', 'binaries')
  ];

  const binaryName = process.platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg';

  for (const basePath of possiblePaths) {
    const ffmpegPath = path.join(basePath, binaryName);
    if (fs.existsSync(ffmpegPath)) {
      unifiedLog(`找到FFmpeg: ${ffmpegPath}`);
      return ffmpegPath;
    }
  }

  // 如果找不到内置的FFmpeg，返回系统PATH中的ffmpeg
  return 'ffmpeg';
}

// FFmpeg 可用性检测
ipcMain.handle("check-ffmpeg", async () => {
  return new Promise((resolve) => {
    const ffmpegPath = getFFmpegPath();
    const ffmpegProcess = spawn(ffmpegPath, ["-version"], {
      stdio: ["pipe", "pipe", "pipe"],
    });

    let output = "";

    ffmpegProcess.stdout.on("data", (data) => {
      output += data.toString();
    });

    ffmpegProcess.stderr.on("data", (data) => {
      output += data.toString();
    });

    ffmpegProcess.on("close", (code) => {
      if (code === 0 && output.includes("ffmpeg version")) {
        unifiedLog("FFmpeg 检测成功");
        resolve({
          available: true,
          version: output.split('\n')[0],
          message: "FFmpeg 可用",
          path: ffmpegPath
        });
      } else {
        unifiedLog("FFmpeg 检测失败", 'error');
        resolve({
          available: false,
          version: null,
          message: "FFmpeg 未安装或不可用",
          path: ffmpegPath
        });
      }
    });

    ffmpegProcess.on("error", (error) => {
      unifiedLog("FFmpeg 检测错误: " + error.message, 'error');
      resolve({
        available: false,
        version: null,
        message: `FFmpeg 检测错误: ${error.message}`,
        path: ffmpegPath
      });
    });

    // 3秒超时
    setTimeout(() => {
      ffmpegProcess.kill();
      resolve({
        available: false,
        version: null,
        message: "FFmpeg 检测超时",
        path: ffmpegPath
      });
    }, 3000);
  });
});

// 多路采集源实现，使用延迟同步启动
ipcMain.handle("start-video-collection", async (event, options) => {
  const { channels, outputDir, maxDuration, startTime } = options;

  try {
    // 清理之前的进程
    activeCollectionProcesses.forEach((p) => {
      if (!p.process.killed) {
        try {
          p.process.kill("SIGINT");
        } catch (e) {
          console.log("进程已结束");
        }
      }
    });
    activeCollectionProcesses = [];

    // 创建输出目录
    const fs = require("fs");
    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true });
    }

    unifiedLog("准备启动所有采集进程...");

    // 获取FFmpeg路径
    const ffmpegPath = getFFmpegPath();

    // 第一步：准备所有FFmpeg进程配置
    const processConfigs = [];
    for (const channel of channels) {
      // 使用时间戳作为文件名
      const timestamp = path.basename(outputDir); // 获取时间戳文件夹名
      const outputPath = path.join(
        outputDir,
        `${channel.name}_${timestamp}.mp4`
      );

      // 判断是本地文件还是网络流
      const isLocalFile =
        !channel.streamUrl.startsWith("http") &&
        !channel.streamUrl.startsWith("rtmp") &&
        !channel.streamUrl.startsWith("rtsp");

      // 检查本地文件是否存在
      if (isLocalFile && !fs.existsSync(channel.streamUrl)) {
        unifiedLog(`输入文件不存在: ${channel.streamUrl}`, 'error');
        continue;
      }

      // 根据输入类型选择不同的 FFmpeg 参数
      let ffmpegArgs;

      if (isLocalFile) {
        ffmpegArgs = [
          "-re",
          "-stream_loop",
          "-1",
          "-i",
          channel.streamUrl,
          "-c:v",
          "libx264",
          "-preset",
          "fast",
          "-crf",
          "23",
          "-profile:v",
          "baseline",
          "-level",
          "3.0",
          "-pix_fmt",
          "yuv420p",
          "-c:a",
          "aac",
          "-b:a",
          "128k",
          "-ar",
          "44100",
          "-ac",
          "2",
          "-movflags",
          "+faststart",
          "-avoid_negative_ts",
          "make_zero",
          "-y",
          outputPath,
        ];
      } else {
        // 网络流参数优化
        ffmpegArgs = [
          // 输入选项 - 针对网络流优化
          "-fflags",
          "+genpts+igndts",
          "-thread_queue_size",
          "2048",
          "-analyzeduration",
          "3000000",
          "-probesize",
          "3000000",
          "-max_delay",
          "3000000",
          "-rtsp_transport",
          "tcp",
          "-rtsp_flags",
          "prefer_tcp",
          "-timeout",
          "10000000",
          "-i",
          channel.streamUrl,
          // 输出选项 - 针对网络流优化
          "-c:v",
          "libx264",
          "-preset",
          "ultrafast",
          "-crf",
          "28",
          "-profile:v",
          "baseline",
          "-level",
          "3.0",
          "-pix_fmt",
          "yuv420p",
          "-c:a",
          "aac",
          "-b:a",
          "128k",
          "-ar",
          "44100",
          "-ac",
          "2",
          "-movflags",
          "+faststart",
          "-avoid_negative_ts",
          "make_zero",
          // 网络重连选项
          "-reconnect",
          "1",
          "-reconnect_streamed",
          "1",
          "-reconnect_delay_max",
          "10",
          "-reconnect_at_eof",
          "1",
          "-reconnect_on_network_error",
          "1",
          "-y",
          outputPath,
        ];
      }

      processConfigs.push({
        channel,
        ffmpegArgs,
        outputPath,
        isLocalFile,
      });
    }

    if (processConfigs.length === 0) {
      throw new Error("没有有效的采集源");
    }

    unifiedLog(`准备启动 ${processConfigs.length} 个采集进程`);

    // 第二步：计算统一启动时间（当前时间 + 1.5秒缓冲）
    const unifiedStartTime = new Date(Date.now() + 1500);
    unifiedLog(`统一启动时间: ${unifiedStartTime.toISOString()}`);

    // 第三步：同时启动所有FFmpeg进程
    const startPromises = processConfigs.map(async (config) => {
      const { channel, ffmpegArgs, outputPath, isLocalFile } = config;

      // 等待到统一启动时间
      const waitTime = unifiedStartTime.getTime() - Date.now();
      if (waitTime > 0) {
        await new Promise((resolve) => setTimeout(resolve, waitTime));
      }

      unifiedLog(
          `启动采集: ${channel.name} (${isLocalFile ? "本地文件" : "网络流"})`
        );
        unifiedLog(`流地址: ${channel.streamUrl}`);
        unifiedLog(`输出文件: ${outputPath}`);

      const ffmpegProcess = spawn(ffmpegPath, ffmpegArgs, {
        stdio: ["pipe", "pipe", "pipe"],
        detached: false,
      });

      const processInfo = {
        process: ffmpegProcess,
        channel: channel.id,
        channelName: channel.name,
        streamUrl: channel.streamUrl,
        outputPath,
        tempPath: outputPath,
        startTime: unifiedStartTime,
        maxDuration: maxDuration,
        isLocalFile: isLocalFile,
        actualStartTime: unifiedStartTime,
      };

      // 监听进程输出
      ffmpegProcess.stderr.on("data", (data) => {
        const output = data.toString();
        // 更详细的日志输出，特别关注网络流错误
        if (
          output.includes("time=") ||
          output.includes("Error") ||
          output.includes("error") ||
          output.includes("Connection") ||
          output.includes("timeout") ||
          output.includes("Connection refused") ||
          output.includes("Network is unreachable") ||
          output.includes("No route to host") ||
          output.includes("Connection timed out") ||
          output.includes("Invalid data found") ||
          output.includes("Stream mapping") ||
          output.includes("Input") ||
          output.includes("Output")
        ) {
          unifiedLog(`FFmpeg ${channel.name}: ${output.trim()}`);
        }
      });

      // 监听进程完成
      ffmpegProcess.on("close", (code, signal) => {
        const endTime = new Date();
        const actualDuration = Math.floor(
          (endTime - processInfo.actualStartTime) / 1000
        );

        unifiedLog(`Channel ${channel.id} (${channel.name}) 采集完成`);
        unifiedLog(`实际采集时长: ${actualDuration}秒`);
        unifiedLog(`退出码: ${code}, 信号: ${signal}`);

        // 延迟检查输出文件，给FFmpeg时间完成写入
        setTimeout(() => {
          unifiedLog(`检查输出文件: ${outputPath}`);

          if (fs.existsSync(outputPath)) {
            const stats = fs.statSync(outputPath);
            const fileSizeMB = (stats.size / 1024 / 1024).toFixed(2);
            unifiedLog(`✓ 视频文件存在: ${outputPath}, 大小: ${fileSizeMB} MB`);

            // 检查文件是否有效（大于1KB）
            if (stats.size > 1024) {
              unifiedLog(`✓ 视频文件有效，大小: ${fileSizeMB} MB`);
            } else {
              unifiedLog(`⚠ 视频文件过小，可能无效: ${fileSizeMB} MB`);
            }
          } else {
            unifiedLog(`✗ 视频文件不存在: ${outputPath}`);

            // 检查目录中的其他文件
            const outputDir = path.dirname(outputPath);
            if (fs.existsSync(outputDir)) {
              const files = fs.readdirSync(outputDir);
              unifiedLog(`目录 ${outputDir} 中的文件: ${JSON.stringify(files)}`);
            }
          }
        }, 2000); // 延迟2秒检查

        // 从活动进程列表中移除
        const index = activeCollectionProcesses.findIndex(
          (p) => p.channel === channel.id
        );
        if (index > -1) {
          activeCollectionProcesses.splice(index, 1);
        }
      });

      ffmpegProcess.on("error", (error) => {
        unifiedLog(`Channel ${channel.id} 进程错误: ${error.message}`, 'error');
      });

      return processInfo;
    });

    // 等待所有进程启动完成
    const processInfos = await Promise.all(startPromises);
    const validProcesses = processInfos.filter((p) => p !== null);
    activeCollectionProcesses.push(...validProcesses);

    unifiedLog(`成功启动 ${validProcesses.length} 个采集进程`);
    unifiedLog(`实际启动时间: ${unifiedStartTime.toISOString()}`);

    // 设置安全超时保护
    setTimeout(() => {
      activeCollectionProcesses.forEach((processInfo) => {
        if (!processInfo.process.killed) {
          unifiedLog(`${processInfo.channelName} 达到最大时长限制，自动停止`);
          processInfo.process.kill("SIGINT");
        }
      });
    }, (maxDuration + 60) * 1000);

    return {
      success: true,
      processes: validProcesses.length,
      outputDir,
      actualStartTime: unifiedStartTime.toISOString(), // 返回实际启动时间
      channels: channels.map((c) => ({ id: c.id, name: c.name })),
    };
  } catch (error) {
    unifiedLog("启动视频采集失败: " + error, 'error');
    return { success: false, error: error.message };
  }
});

// 改进停止采集，确保进程完全退出
ipcMain.handle("stop-video-collection", async (event, options = {}) => {
  const { endTime, actualDuration } = options;

  try {
    // 通知WebSocket服务器恢复RTSP流处理
    unifiedLog("通知WebSocket服务器恢复RTSP流处理");

    let stoppedCount = 0;
    const stopPromises = [];

    unifiedLog(`开始停止 ${activeCollectionProcesses.length} 个采集进程`);
    unifiedLog(`实际采集时长: ${actualDuration}秒`);

    for (const processInfo of activeCollectionProcesses) {
      if (!processInfo.process.killed) {
        const stopPromise = new Promise((resolve) => {
          try {
            unifiedLog(`停止进程: ${processInfo.channelName}`);

            // 尝试优雅停止 FFmpeg
            try {
              // 发送 'q' 命令优雅停止 FFmpeg
              processInfo.process.stdin.write("q\n");
              unifiedLog(`已发送停止命令到进程: ${processInfo.channelName}`);
            } catch (e) {
              unifiedLog(`无法发送停止命令到进程: ${processInfo.channelName} ${e.message}`);
            }

            // 监听进程退出
            processInfo.process.on("close", () => {
              unifiedLog(`进程 ${processInfo.channelName} 已优雅退出`);
              resolve();
            });

            // 如果3秒后还没退出，发送SIGINT信号
            setTimeout(() => {
              if (!processInfo.process.killed) {
                unifiedLog(`发送SIGINT信号到进程: ${processInfo.channelName}`);
                try {
                  processInfo.process.kill("SIGINT");
                } catch (e) {
                  unifiedLog(`SIGINT失败: ${processInfo.channelName} ${e.message}`);
                }
              }
            }, 3000);

            // 如果8秒后还没退出，强制终止
            setTimeout(() => {
              if (!processInfo.process.killed) {
                unifiedLog(`强制终止进程: ${processInfo.channelName}`);
                try {
                  processInfo.process.kill("SIGTERM");
                } catch (e) {
                  unifiedLog(`SIGTERM失败: ${processInfo.channelName} ${e.message}`);
                }
                resolve();
              }
            }, 8000);

            stoppedCount++;
          } catch (e) {
            unifiedLog(`进程 ${processInfo.channelName} 已结束`);
            resolve();
          }
        });

        stopPromises.push(stopPromise);
      }
    }

    // 等待所有进程退出
    await Promise.all(stopPromises);

    // 清空进程列表
    activeCollectionProcesses = [];

    unifiedLog(`所有采集进程已停止 (${stoppedCount} 个)`);

    return {
      success: true,
      message: `已停止 ${stoppedCount} 个采集进程`,
      endTime,
      actualDuration,
    };
  } catch (error) {
    unifiedLog("停止采集进程失败: " + error, 'error');
    return { success: false, error: error.message };
  }
});

// 获取当前采集状态
ipcMain.handle("get-collection-status", async (event) => {
  return {
    isCollecting: activeCollectionProcesses.length > 0,
    activeChannels: activeCollectionProcesses.map((p) => ({
      channel: p.channel,
      name: p.channelName,
      streamUrl: p.streamUrl,
    })),
  };
});

// 改进文件保存，增加重试机制和文件夹重命名
ipcMain.handle("finalize-collection", async (event, options) => {
  const { outputDir, fileNames, collectionInfo } = options;
  const fs = require("fs");
  const path = require("path");

  try {
    unifiedLog("开始完成采集处理...");
    unifiedLog(`输出目录: ${outputDir}`);
    unifiedLog(`文件名配置: ${JSON.stringify(fileNames)}`);

    // 检查源目录是否存在
    if (!fs.existsSync(outputDir)) {
      unifiedLog(`源目录不存在: ${outputDir}`, 'error');
      return { success: false, error: "采集目录不存在" };
    }

    // 使用时间戳作为最终文件夹名
    const finalFolderName = collectionInfo.finalFolderName;
    const parentDir = path.dirname(outputDir);
    const finalOutputDir = path.join(parentDir, finalFolderName);

    unifiedLog(`最终目录: ${finalOutputDir}`);

    // 如果最终目录名与当前目录名相同，则不需要重命名
    if (outputDir === finalOutputDir) {
      unifiedLog("目录名相同，无需重命名");
      return {
        success: true,
        message: "文件保存完成",
        finalPath: finalOutputDir,
      };
    }

    // 如果最终目录已存在，先删除
    if (fs.existsSync(finalOutputDir)) {
      unifiedLog(`删除已存在的目标目录: ${finalOutputDir}`);
      fs.rmSync(finalOutputDir, { recursive: true, force: true });
    }

    // 重命名目录
    unifiedLog(`重命名目录: ${outputDir} -> ${finalOutputDir}`);
    fs.renameSync(outputDir, finalOutputDir);

    unifiedLog("采集完成处理成功");
    return {
      success: true,
      message: "文件保存完成",
      finalPath: finalOutputDir,
    };
  } catch (error) {
    unifiedLog(`完成采集处理失败: ${error}`, 'error');
    return {
      success: false,
      error: error.message,
    };
  }
});

// 简单的开发环境检测
const isDev = process.argv.includes("--dev") || !app.isPackaged;

function createWindow() {
  const startupMessages = [
    "Electron启动中...",
    `开发模式: ${isDev}`,
    `Electron version: ${process.versions.electron}`,
    `Chromium version: ${process.versions.chrome}`
  ];
  
  // 同时输出到控制台和日志文件
  startupMessages.forEach(message => {
    unifiedLog(message);
    logToFile(message);
  });

  // 创建浏览器窗口
  mainWindow = new BrowserWindow({
    width: 1400,
    height: 900,
    minWidth: 1200,
    minHeight: 800,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, "preload.js"),
      webSecurity: false, // 允许访问本地文件
    },
    show: false,
    title: "信息采集与分析系统v1.0.1",
  });

  unifiedLog("窗口创建完成");

  // 隐藏菜单栏
  Menu.setApplicationMenu(null);

  // 加载应用
  if (isDev) {
    unifiedLog("加载开发服务器: http://localhost:5174");
    mainWindow.loadURL("http://localhost:5174");
    // 开发环境下打开开发者工具
    mainWindow.webContents.openDevTools();
  } else {
    const indexPath = path.join(__dirname, "../dist/index.html");
    unifiedLog(`加载生产文件: ${indexPath}`);
    mainWindow.loadFile(indexPath);
  }

  // 页面加载完成后，在渲染进程控制台打印版本，便于打包后查看
  mainWindow.webContents.on('did-finish-load', () => {
    const eVer = process.versions.electron;
    const cVer = process.versions.chrome;
    unifiedLog(`[Main] Electron: ${eVer}, Chromium: ${cVer}`);
    mainWindow.webContents.executeJavaScript(
      `console.log('Electron version:', '${eVer}'); console.log('Chromium version:', '${cVer}');`
    ).catch(() => {});

    // 确保开发者工具已打开，方便查看渲染进程打印（生产环境也打开一次）
    if (!mainWindow.webContents.isDevToolsOpened()) {
      try { mainWindow.webContents.openDevTools({ mode: 'detach' }); } catch (e) {}
    }
  });

  // 窗口准备好后显示
  mainWindow.once("ready-to-show", () => {
    mainWindow.show();

    // 自动启动RTSP流服务器
    // setTimeout(() => {
    //   startRTSPServer();
    // }, 2000); // 延迟2秒启动，确保应用完全加载
  });

  // 当窗口关闭时触发
  mainWindow.on("closed", () => {
    mainWindow = null;
  });

  // 添加错误处理
  mainWindow.webContents.on(
    "did-fail-load",
    (event, errorCode, errorDescription) => {
      unifiedLog(`页面加载失败: ${errorCode} ${errorDescription}`, 'error');

      // 如果加载失败，显示错误页面
      const errorHtml = `
      <!DOCTYPE html>
      <html>
      <head>
        <title>信息采集与分析系统 - 错误</title>
        <style>
          body { 
            font-family: Arial, sans-serif; 
            display: flex; 
            justify-content: center; 
            align-items: center; 
            height: 100vh; 
            margin: 0; 
            background: #f5f5f5;
          }
          .container { 
            text-align: center; 
            padding: 40px;
            background: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            max-width: 500px;
          }
          .error { color: #e74c3c; margin-bottom: 20px; }
          .solution { color: #27ae60; }
          button {
            background: #3498db;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
            margin: 5px;
          }
        </style>
      </head>
      <body>
        <div class="container">
          <h1>信息采集与分析系统</h1>
          <div class="error">
            <h3>⚠️ 应用启动失败</h3>
            <p>无法加载应用页面</p>
          </div>
          <div class="solution">
            <h4>解决方案：</h4>
            <p>1. 确保开发服务器正在运行：<code>npm run dev</code></p>
            <p>2. 或者先构建项目：<code>npm run build</code></p>
          </div>
          <button onclick="location.reload()">重新加载</button>
        </div>
      </body>
      </html>
    `;
      mainWindow.loadURL(
        `data:text/html;charset=utf-8,${encodeURIComponent(errorHtml)}`
      );
    }
  );
}

// UDP 处理函数
function handleUdp() {
  // 用于发送给渲染端的队列（仅包含匹配taskid的数据）
  let renderQueue = [];
  // 用于保存到数据库的队列（包含所有数据）
  let dbQueue = [];
  // 用于保存到文件的队列（包含所有数据）
  let fileQueue = [];
  // 发送给渲染端的定时器
  let renderTimer = null;
  // 保存到数据库的定时器
  let dbTimer = null;
  // 保存到文件的定时器
  let fileTimer = null;
  // 从配置文件获取数据处理配置
  const DATA_PROCESSING_CONFIG = electronConfig.APP_CONFIG.DATA_PROCESSING;
  unifiedLog(`DATA_PROCESSING_CONFIG: ${JSON.stringify(DATA_PROCESSING_CONFIG)}`);
  // 发送给渲染端的大小阈值
  let RENDER_BATCH_SIZE = DATA_PROCESSING_CONFIG.RENDER_BATCH_SIZE;
  // 保存到数据库的大小阈值
  let DB_BATCH_SIZE = DATA_PROCESSING_CONFIG.DB_BATCH_SIZE;
  // 保存到文件的大小阈值
  let FILE_BATCH_SIZE = DATA_PROCESSING_CONFIG.FILE_BATCH_SIZE;
  // 发送给渲染端的时间阈值(毫秒)
  let RENDER_BATCH_INTERVAL = DATA_PROCESSING_CONFIG.RENDER_BATCH_INTERVAL;
  // 保存到数据库的时间阈值(毫秒)
  let DB_BATCH_INTERVAL = DATA_PROCESSING_CONFIG.DB_BATCH_INTERVAL;
  // 保存到文件的时间阈值(毫秒)
  let FILE_BATCH_INTERVAL = DATA_PROCESSING_CONFIG.FILE_BATCH_INTERVAL;
  let telemetryDataSaveFileFolder = null;
  // 处理并保存数据到数据库的函数
  async function processAndSaveData() {
    if (dbQueue.length === 0) return;

    // 取出当前队列中的所有数据并清空队列
    const batchData = [...dbQueue];
    dbQueue = [];

    try {
      // 调用 API 保存数据
      // 将数据转换为符合要求的格式
      const formattedData = batchData.map(item => ({
        telemetryTaskId: item.taskId.toString(),
        telemetryData: item.data
      }));
      
      unifiedLog(`准备批量保存的遥测数据: ${JSON.stringify(formattedData)}`);
      // 根据配置设置正确的请求路径
      // 由于已从electron-config.js获取了baseURL，这里使用相对路径
      const result = await axios.post('/system/videoData/batchSaveTelemetryData', formattedData);
      unifiedLog(`成功保存 ${batchData.length} 条遥测数据记录到数据库 ${JSON.stringify(result.data)}`);
    } catch (error) {
      unifiedLog(`保存数据到数据库失败: ${error}`, 'error');
      // 可以在这里处理保存失败的逻辑，比如重试或记录错误
      // 如果保存失败，考虑将数据重新加入队列或记录到错误日志
    }
  }

  // 调用服务端接口保存数据到文件的函数
  async function writeDataToFile() {
    if (fileQueue.length === 0) return;

    // 取出当前队列中的所有数据并清空队列
    const batchData = [...fileQueue];
    fileQueue = [];
    unifiedLog(`调用服务端接口保存数据到文件, 数据量: ${batchData.length}`);
    try {
      // 调用 API 保存数据到文件
      // 使用safeJsonStringify处理可能包含BigInt的数据
      const fileData = JSON.parse(safeJsonStringify(batchData));
      
      // 按照期望格式构建数据：数组中包含一个对象，该对象有telemetryDataSaveFileFolder和telemetryList属性
      let requestData = {
        telemetryDataSaveFileFolder: telemetryDataSaveFileFolder,
        telemetryList: fileData
      };
      requestData=JSON.stringify(requestData, null, 2);
      // 在控制台中以展开形式显示数据，方便调试
      unifiedLog(`准备批量保存的文件数据: ${requestData}`);
      // 根据配置设置正确的请求路径，显式设置Content-Type为application/json确保JSON格式传输
      const result = await axios.post('/system/videoData/generateTelemetryDataFile', requestData, {
        headers: {
          'Content-Type': 'application/json'
        }
      });
      unifiedLog(`成功保存 ${fileData.length} 条数据到文件 ${JSON.stringify(result.data)}`);
    } catch (error) {
      unifiedLog(`保存数据到文件失败: ${error}`, 'error');
      // 可以在这里处理保存失败的逻辑，比如重试或记录错误
    }
  }

  // 发送数据到渲染端的函数
  function sendToRenderer() {
    if (renderQueue.length === 0) return;

    if (mainWindow && !mainWindow.isDestroyed()) {
      // 发送队列中的所有数据
      mainWindow.webContents.send('udp-data', {
        data: [...renderQueue],  // 发送队列的副本，避免数据在发送过程中被修改
        queueLength: renderQueue.length
      });
      unifiedLog(`成功发送 ${renderQueue.length} 条数据到渲染端 `);
      // 清空渲染队列
      renderQueue = [];
    }
  }

  function handleTelemetryData (hexData,confirmedSelectedParam){
    try { 
      // 定义字段偏移量常量
      const OFFSET_TASKID = 16;
      const OFFSET_SYSTIME_H = 48;
      const OFFSET_SYSTIME_M = 52;
      const OFFSET_SYSTIME_S = 56;
      const OFFSET_SYSTIME_MS = 60;
      const OFFSET_DATA_H = 64;
      const OFFSET_DATA_M = 68;
      const OFFSET_DATA_S = 72;
      const OFFSET_DATA_MS = 76;
      const OFFSET_PACKLEN = 0;
      
      // 清理十六进制字符串
      const hexString= hexData.replace(/\s+/g, '');
      unifiedLog(`[遥测数据解析] 清理后的十六进制字符串: ${hexString}`);
      // 验证输入是否为有效的十六进制字符串
      if (!/^[0-9A-Fa-f]+$/.test(hexString)) {
          unifiedLog('[遥测数据解析] 无效的十六进制字符串');
      }
      
      // 十六进制转字节数组
      const bytes = new Uint8Array(hexString.length / 2);
      for (let i = 0; i < bytes.length; i++) {
          bytes[i] = parseInt(hexString.substr(i * 2, 2), 16);
      }
      
      // 创建DataView用于解析
      const view = new DataView(bytes.buffer);
      // 解析参数
      const paramData = [];
      // 解析任务ID（offset=16）
      let receivedTaskId;
      let receivedSysTimeH;
      let receivedSysTimeM;
      let receivedSysTimeS;
      let receivedSysTimeMS;
      let receivedDataH;
      let receivedDataM;
      let receivedDataS;
      let receivedDataMS;
      try {
          unifiedLog(`start解析任务ID: ${OFFSET_TASKID}`);
          receivedTaskId = view.getUint32(OFFSET_TASKID, true);
          unifiedLog(`[遥测数据解析] 解析任务ID: ${receivedTaskId}`);
          // 读取XML配置
          const paramConfigs = confirmedSelectedParam;
          unifiedLog(`[遥测数据解析] 选中的参数配置: ${JSON.stringify(paramConfigs)}`);
          if (paramConfigs.length > 0) {
              // 简化日志输出
              unifiedLog(`\n任务ID: ${receivedTaskId}的参数解析结果`);
              
              // 遍历每个Param标签
              paramConfigs.forEach(config => {
                  unifiedLog(`[遥测数据解析] 解析参数: ${config.ParaID}, ${config.CNName}, ${config.ParaName}, ${config.Offset}`);
                  // 根据Offset解析数据
                  const offsetValue = parseValueByOffset(bytes, config.Offset, config.DataType);
                  
                  // 获取原始字节值
                  let offsetBytes = 'N/A';
                  if (typeof config.Offset === 'number' && config.Offset >= 0 && config.Offset + 4 <= bytes.length) {
                      offsetBytes = Array.from(bytes.slice(config.Offset, config.Offset + 4))
                          .map(b => b.toString(16).padStart(2, '0'))
                          .join(' ');
                  }
                  
                  // 添加到参数数组
                  paramData.push({
                      ParaID: config.ParaID,
                      CNName: config.CNName,
                      ParaName: config.ParaName,
                      Offset: config.Offset,
                      value: offsetValue,
                      rawBytes: offsetBytes
                  });
                  
                  // 简化日志输出
                  unifiedLog(`| ${config.ParaID.padEnd(6)} | ${config.CNName.padEnd(6)} | ${config.ParaName.padEnd(8)} | ${String(config.Offset).padEnd(6)} | ${offsetValue !== null ? String(offsetValue).padEnd(6) : 'N/A'.padEnd(6)} | ${offsetBytes.padEnd(10)} |`);
              });
          } else {
              unifiedLog('未找到Param标签配置');
          }
          receivedSysTimeH = view.getUint32(OFFSET_SYSTIME_H, true);
          receivedSysTimeM = view.getUint32(OFFSET_SYSTIME_M, true);
          receivedSysTimeS = view.getUint32(OFFSET_SYSTIME_S, true);
          receivedSysTimeMS = view.getUint32(OFFSET_SYSTIME_MS, true);
          receivedDataH = view.getUint32(OFFSET_DATA_H, true);
          receivedDataM = view.getUint32(OFFSET_DATA_M, true);
          receivedDataS = view.getUint32(OFFSET_DATA_S, true);
          receivedDataMS = view.getUint32(OFFSET_DATA_MS, true);
          unifiedLog( `[遥测数据解析] 任务ID(taskId): ${receivedTaskId}`);
          unifiedLog( `[遥测数据解析] 系统时间(HH:MM:SS:MS): ${receivedSysTimeH}:${receivedSysTimeM}:${receivedSysTimeS}:${receivedSysTimeMS}`);
          unifiedLog( `[遥测数据解析] 数据时间(HH:MM:SS:MS): ${receivedDataH}:${receivedDataM}:${receivedDataS}:${receivedDataMS}`);
          
      } catch (error) {
          unifiedLog('[遥测数据解析] 无法解析任务ID');
      }      
      
      // 返回结果
      return {
          taskid: receivedTaskId,
          sysTimeH: receivedSysTimeH,
          sysTimeM: receivedSysTimeM,
          sysTimeS: receivedSysTimeS,
          sysTimeMS: receivedSysTimeMS,
          dataTimeH: receivedDataH,
          dataTimeM: receivedDataM,
          dataTimeS: receivedDataS,
          dataTimeMS: receivedDataMS,
          param: paramData
      };
        
    } catch (error) {
        unifiedLog( `[遥测数据解析] 解析遥测数据时出错: ${error.message}`, 'error');
        return null;
    }
}
    
// 根据Offset值从字节数组中解析数据
function parseValueByOffset(bytesArray, offset, dataType){
    try {
      // 防御性编程：确保dataType是数字类型
      const numDataType = typeof dataType === 'string' ? parseInt(dataType, 10) : dataType;
      
      // 验证dataType是否有效
      if (typeof numDataType !== 'number' || isNaN(numDataType) || numDataType < 0 || numDataType > 5) {
          unifiedLog(`[Offset解析] 无效的数据类型参数: ${dataType} (类型: ${typeof dataType})`);
          return null;
      }
      
      // 验证偏移量是否有效
      // 根据numDataType确定需要的字节长度
      let byteLength = 4; // 默认4字节
      if (numDataType === 3 || numDataType === 4 || numDataType === 5) {
          byteLength = 8; // 64位数据需要8字节
      }
        
    if (typeof offset !== 'number' || offset < 0 || offset + byteLength > bytesArray.length) {
        unifiedLog(`[Offset解析] 无效的偏移量: ${offset}，数据类型: ${numDataType}`);
        return null;
    }
    
    // 创建DataView并根据numDataType解析不同类型的数据（小端序）
    const view = new DataView(bytesArray.buffer);
    let value = null;
    console.log(`[Offset解析] 解析Offset=${offset}处的数据(数据类型: ${numDataType})`);
    switch(numDataType) {
        case 0: // 无符号整数
            value = view.getUint32(offset, true);
            break;
        case 1: // 有符号整数
            value = view.getInt32(offset, true);
            break;
        case 2: // 浮点数
            value = view.getFloat32(offset, true);
            break;
        case 3: // 双精度
            value = view.getFloat64(offset, true);
            break;
        case 4: // 有符号长整数
            // JavaScript没有直接的64位整数支持，这里使用BigInt
            const lowInt = view.getUint32(offset, true);
            const highInt = view.getInt32(offset + 4, true);
            value = BigInt(highInt) << 32n | BigInt(lowInt);
            break;
        case 5: // 无符号长整数
            // JavaScript没有直接的64位整数支持，这里使用BigInt
            const lowUint = view.getUint32(offset, true);
            const highUint = view.getUint32(offset + 4, true);
            value = BigInt(highUint) << 32n | BigInt(lowUint);
            break;
        default:
            unifiedLog(`[Offset解析] 不支持的数据类型: ${numDataType}`);
            return null;
    }
    
    unifiedLog(`[Offset解析] 成功解析Offset=${offset}处的值(数据类型: ${numDataType}): ${value}`);
    return value;
  } catch (error) {
    unifiedLog(`[Offset解析] 解析Offset=${offset}处的数据时出错: ${error.message}`, 'error');
    return null;
  }
}

  // 启动 UDP 监听
  ipcMain.handle('start-udp-listener', async (event, { address, port ,selectedTaskId,allParam,confirmedSelectedParam,telemetryDataSaveFileFolder: currentTelemetryDataSaveFileFolder}) => {
    try {
      // 如果已有 socket，先关闭
      if (udpSocket) {
        udpSocket.close();
        udpSocket = null;
      }
      unifiedLog(`selectedTaskId: ${selectedTaskId}`);
      // 反序列化参数配置
      let deserializedSelectedParams = null;
      let deserializedAllParams = null;
      try {
        deserializedSelectedParams = JSON.parse(confirmedSelectedParam);
        deserializedAllParams = JSON.parse(allParam);
        unifiedLog(`反序列化后的confirmedSelectedParam: ${JSON.stringify(deserializedSelectedParams)}`);
        unifiedLog(`反序列化后的allParam: ${JSON.stringify(deserializedAllParams)}`);
      } catch (e) {
        unifiedLog(`参数反序列化失败: ${e.message}`, 'error');
      }
      telemetryDataSaveFileFolder=currentTelemetryDataSaveFileFolder;
      // 重置数据队列和定时器
      renderQueue = [];
      dbQueue = [];
      if (renderTimer) {
        clearInterval(renderTimer);
        renderTimer = null;
      }
      if (dbTimer) {
        clearInterval(dbTimer);
        dbTimer = null;
      }
      // 创建 UDP socket
      udpSocket = dgram.createSocket('udp4');
      udpSocket.on('message', (msg, rinfo) => {
          // 定义字段偏移量常量
        const OFFSET_TASKID = 16;
        // 清理十六进制字符串
        const hexString= msg.toString('hex');
        // 验证输入是否为有效的十六进制字符串
        if (/^[0-9A-Fa-f]+$/.test(hexString)) {
            // 十六进制转字节数组
          const bytes = new Uint8Array(hexString.length / 2);
          for (let i = 0; i < bytes.length; i++) {
              bytes[i] = parseInt(hexString.substr(i * 2, 2), 16);
          }          
          // 创建DataView用于解析
          const view = new DataView(bytes.buffer);
          // 解析任务ID（offset=16）
          let receivedTaskId;        
          receivedTaskId = view.getUint32(OFFSET_TASKID, true);
          // 类型转换，确保比较一致
          const selectedTaskIdStr = String(selectedTaskId);
          const receivedTaskIdStr = String(receivedTaskId);
          
                             
          unifiedLog(`selectedTaskIdStr: ${selectedTaskIdStr}`);
          unifiedLog(`receivedTaskIdStr: ${receivedTaskIdStr}`);
          // 如果taskid匹配，加入渲染队列
          if(selectedTaskIdStr === receivedTaskIdStr){
            
            // 解析选中的遥测数据参数
            const parsedData = handleTelemetryData(hexString, deserializedSelectedParams);

            renderQueue.push(parsedData);
            unifiedLog(`renderQueue length: ${renderQueue.length}`);
            
            // 当渲染队列达到阈值时，立即发送到渲染端
            if (renderQueue.length >= RENDER_BATCH_SIZE) {
              // 清除现有定时器，立即发送
              if (renderTimer) {
                clearInterval(renderTimer);
              }
              sendToRenderer();
              // 重新设置渲染定时器
              renderTimer = setInterval(sendToRenderer, RENDER_BATCH_INTERVAL);
            }
            // 构建数据对象
            const dbDataItem = {
              data: hexString,
              timestamp: new Date().toISOString(),
              source: `${rinfo.address}:${rinfo.port}`,
              taskId: receivedTaskId
            };  
            // 加入数据库队列
            dbQueue.push(dbDataItem);
            unifiedLog(`dbQueue length: ${dbQueue.length}`);          
            // 当数据库队列达到阈值时，立即处理
            if (dbQueue.length >= DB_BATCH_SIZE) {
              // 清除现有定时器，立即处理
              if (dbTimer) {
                clearInterval(dbTimer);
              }
              processAndSaveData();
              // 重新设置数据库定时器
              dbTimer = setInterval(processAndSaveData, DB_BATCH_INTERVAL);
            }
            // 构建数据对象
            // 过滤paramData，只保留需要的字段
            let filteredParamData = [];
            // 解析所有的遥测数据参数
            const parsedAllParaData = handleTelemetryData(hexString, deserializedAllParams);
            // 添加空值检查，防止访问undefined对象的属性
            if (parsedAllParaData && parsedAllParaData.param && Array.isArray(parsedAllParaData.param)) {
              filteredParamData = parsedAllParaData.param.map(param => ({
                paraID: param.ParaID,
                paraValue: param.value,
                paraName: (param.CNName && param.CNName.trim() !== '' ? param.CNName : param.ParaName).replace(/\//g, '_')
              }));
            } else {
              unifiedLog(`[数据处理] parsedAllParaData或parsedAllParaData.param为空或不是数组: ${JSON.stringify(parsedAllParaData)}`);
            }
            
            // 先对paramData进行JSON序列化，使用safeJsonStringify处理可能包含BigInt的数据
            const serializedAllParamData = JSON.parse(safeJsonStringify(filteredParamData));
            
            const fileAllParaDataItem = {              
              receivedTaskId: receivedTaskId,              
              receivedSysTimeH: parsedAllParaData?.sysTimeH || 0,
              receivedSysTimeM: parsedAllParaData?.sysTimeM || 0,
              receivedSysTimeS: parsedAllParaData?.sysTimeS || 0,
              receivedSysTimeMS: parsedAllParaData?.sysTimeMS || 0,
              paramData: serializedAllParamData             
            };  
            // 加入文件队列
            fileQueue.push(fileAllParaDataItem);
            unifiedLog(`fileQueue length: ${fileQueue.length}`);          
            // 当文件队列达到阈值时，立即处理
            if (fileQueue.length >= FILE_BATCH_SIZE) {
              // 清除现有定时器，立即处理
              if (fileTimer) {
                clearInterval(fileTimer);
              }
              writeDataToFile();
              // 重新设置文件定时器
              fileTimer = setInterval(writeDataToFile, FILE_BATCH_INTERVAL);
            }
          }          
        }else{
          unifiedLog('[遥测数据解析] 无效的十六进制字符串');
        }          
      });

      udpSocket.on('error', (err) => {
        unifiedLog(`UDP Socket error: ${JSON.stringify(err)}`, 'error');
        if (mainWindow && !mainWindow.isDestroyed()) {
          mainWindow.webContents.send('udp-error', err.message);
        }
      });

      udpSocket.on('listening', () => {
        unifiedLog("start listing");
        unifiedLog(`UDP Socket listening on ${address}:${port}`);
        try {
          unifiedLog("start join")
          unifiedLog(`join: ${address}`)
          // 加入组播
          udpSocket.addMembership(address);
          unifiedLog(`addMembership join ok: ${address}`);

          // 启动批量处理定时器
          renderTimer = setInterval(sendToRenderer, RENDER_BATCH_INTERVAL);
          dbTimer = setInterval(processAndSaveData, DB_BATCH_INTERVAL);
          fileTimer = setInterval(writeDataToFile, FILE_BATCH_INTERVAL);
        } catch (joinError) {
          unifiedLog(`加入组播失败: ${JSON.stringify(joinError)}`, 'error');
          if (mainWindow && !mainWindow.isDestroyed()) {
            mainWindow.webContents.send('udp-error', {
              message: `加入组播失败: ${joinError.message}`,
              code: joinError.code,
              address: address
            });
          }
        }
      });
      unifiedLog('start bind');
      // 绑定端口
      unifiedLog(`port: ${port}`);
      udpSocket.bind(port);  // 使用指定的绑定地址
      unifiedLog('bind ok');
      return { success: true, message: 'UDP listener started' };
    } catch (error) {
      return { success: false, message: error.message };
    }
  });

  // 停止 UDP 监听
  ipcMain.handle('stop-udp-listener', async () => {
    try {
      // 清除定时器
      if (renderTimer) {
        clearInterval(renderTimer);
        renderTimer = null;
      }
      if (dbTimer) {
        clearInterval(dbTimer);
        dbTimer = null;
      }
      if (fileTimer) {
        clearInterval(fileTimer);
        fileTimer = null;
      }

      // 处理剩余数据
      // 发送剩余的渲染数据
      if (renderQueue.length > 0) {
        sendToRenderer();
      }
      // 保存剩余的数据库数据
      if (dbQueue.length > 0) {
        await processAndSaveData();
      }
      // 保存剩余的文件数据
      if (fileQueue.length > 0) {
        await writeDataToFile();
      }

      if (udpSocket) {
        udpSocket.close();
        udpSocket = null;
      }
      return { success: true, message: 'UDP listener stopped' };
    } catch (error) {
      return { success: false, message: error.message };
    }
  });
}


//TCP相关
let tcpDownloader = null;

// 初始化客户端
ipcMain.handle('tcp-connect', async (event, { host, port}) => {
  try {
    if (!tcpDownloader) {
      tcpDownloader = new TcpConfigDownloader();
      // 设置数据解析完成回调
      tcpDownloader.onDataParsed = (data) => {
        // 这里可以添加额外的处理逻辑，如果需要的话
        unifiedLog(`[TCP] 数据解析完成: ${JSON.stringify(data)}`);
      };
      // 设置下载完成回调
      tcpDownloader.onDownloadComplete = (result) => {
        // 这里可以添加额外的处理逻辑，如果需要的话
        unifiedLog(`[TCP] 下载完成: ${JSON.stringify(result)}`);
      };
      // 设置日志回调，将日志发送到渲染进程
      tcpDownloader.logCallback = (level, message) => {
        // 使用tcpBroadcastLog函数将日志发送到渲染进程
        tcpBroadcastLog(`${message}`);
      };
    }
    await tcpDownloader.connect(host, port);
    tcpBroadcastStatus('connected');
    return { success: true, message: 'tcp连接成功' };
  } catch (err) {
    tcpBroadcastStatus('disconnected');
    return { success: false, message: err.message };
  }
});

ipcMain.handle('tcp-disconnect', async () => {
  if (tcpDownloader) {
    await tcpDownloader.disconnect();
    tcpBroadcastStatus('disconnected');
  }
});

ipcMain.handle('tcp-config-download', async (event, payload) => {
  if (!tcpDownloader || !tcpDownloader.isConnected) {
    unifiedLog('[TCP日志] tcp未连接，无法发送请求');
    return { success: false, message: 'tcp未连接服务器' };
  }
  try {
    const {cmdType, taskID } = payload; // 获取endian参数，默认为big
    // 保存当前的tcpDownloader引用，避免闭包问题
    const currentTcpDownloader = tcpDownloader;
    
    // 处理JSON字符串格式的taskID数组
    let taskIds = taskID;
    if (cmdType === 2 && typeof taskIds === 'string') {
      try {
        taskIds = JSON.parse(taskIds);
      } catch (e) {
        unifiedLog(`[TCP日志] 解析taskID数组失败: ${e.message}`, 'error');
        return { success: false, message: `解析taskID数组失败: ${e.message}` };
      }
    }
    
    // 如果cmdType=2且taskIds是数组，则遍历下载每个任务配置文件
    if (cmdType === 2 && Array.isArray(taskIds)) {
      unifiedLog(`[TCP日志] 开始批量下载任务配置文件，共${taskIds.length}个任务`);
      
      const results = [];
      let successCount = 0;
      let failCount = 0;
      
      // 遍历任务ID数组
      for (const taskId of taskIds) {
        // 如果taskId等于110，只打印日志并跳过下载
        if (taskId === 110) {
          unifiedLog(`[TCP日志] 任务id为110的任务配置文件已忽略下载`);
          continue;
        }
        
        try {
          unifiedLog(`[TCP日志] 正在下载任务ID: ${taskId}的配置文件`);
          // 构建请求数据包
          const packet = currentTcpDownloader.buildRequest(cmdType, taskId);
          unifiedLog(`[TCP日志] 任务ID: ${taskId} - 请求数据包构建完成`);
          
          // 清理请求队列，确保不会有残留的Promise
          while (currentTcpDownloader.requestQueue && currentTcpDownloader.requestQueue.length > 0) {
            const { reject } = currentTcpDownloader.requestQueue.shift();
            reject(new Error('请求已被取消，队列正在清理'));
          }
          
          // 发送请求并等待响应（增加超时时间）
          tcpBroadcastLog(`任务ID: ${taskId} - 开始发送请求`);
          const resultPromise = currentTcpDownloader.processRequest(packet);
          const timeoutPromise = new Promise((_, reject) => {
            setTimeout(() => reject(new Error(`任务ID: ${taskId} 下载超时`)), 30000); // 60秒超时
          });
          
          // 使用Promise.race处理超时
          let result;
          try {
            result = await Promise.race([resultPromise, timeoutPromise]);
            results.push(result);
            tcpBroadcastLog(`任务ID: ${taskId} - 请求处理完成`);
          } catch (timeoutError) {
            // 发送超时信息到前端，检查是否是最后一个任务
          const isLastTask = taskIds.indexOf(taskId) === taskIds.length - 1;
          tcpBroadcastLog(`任务ID: ${taskId} - 请求超时${isLastTask ? '' : '，准备继续处理下一个文件'}`);
            // 清理请求队列
            if (currentTcpDownloader.requestQueue && currentTcpDownloader.requestQueue.length > 0) {
              currentTcpDownloader.requestQueue = [];
            }
            // 记录失败，但不抛出异常，继续处理下一个文件
            results.push({ success: false, message: timeoutError.message, taskId });
            failCount++;
            unifiedLog(`[TCP日志] 下载任务ID: ${taskId}的配置文件超时: ${timeoutError.message}`, 'error');
            // 继续处理下一个任务
            continue;
          }
          
          if (result.success) {
            successCount++;
            unifiedLog(`[TCP日志] 任务ID: ${taskId} - 配置文件下载成功`);
          } else {
            failCount++;
            unifiedLog(`[TCP日志] 下载任务ID: ${taskId}的配置文件失败: ${result.message}`, 'error');
          }
        } catch (err) {
          failCount++;
          results.push({ success: false, message: err.message, taskId });
          unifiedLog(`[TCP日志] 下载任务ID: ${taskId}的配置文件发生异常: ${err.message}`, 'error');
          // 即使出错，也继续处理下一个任务
          continue;
        }
      }
      
      unifiedLog(`[TCP日志] 批量下载任务配置文件完成，成功${successCount}个，失败${failCount}个`);
      
      return {
        success: failCount === 0,
        message: failCount === 0 ? '所有任务配置文件下载成功' : `部分任务配置文件下载失败，成功${successCount}个，失败${failCount}个`,
        results: results,
        successCount: successCount,
        failCount: failCount
      };
    } else {
      // 构建请求数据包
      const packet = currentTcpDownloader.buildRequest(cmdType, taskID);
      unifiedLog('[TCP日志] 请求数据包已发送');
      // 使用processRequest方法处理请求，它会返回一个Promise并在数据处理完成后resolve或reject
      return await currentTcpDownloader.processRequest(packet);
    }
  } catch (err) {
    unifiedLog(`[TCP日志] tcp-config-download请求异常: ${err.message}`, 'error');
    return { success: false, message: err.message };
  }
});

// 增强的日志广播函数，确保即使在窗口状态不稳定时也能记录日志
function tcpBroadcastStatus(status) {
  try {
    if (mainWindow && !mainWindow.isDestroyed()) {
      mainWindow.webContents.send('tcp-status-change', status);
    } else {
      unifiedLog(`[TCP状态] ${status} (窗口不可用)`);
    }
  } catch (error) {
    unifiedLog(`发送TCP状态失败: ${error.message}`, 'error');
  }
}

// 增强的日志广播函数，确保即使在窗口状态不稳定时也能记录日志
function tcpBroadcastLog(message) {
  try {
    // 先输出到主进程控制台
    unifiedLog(`[TCP日志] ${message}`);
    
    // 再尝试发送到渲染进程控制台
    if (mainWindow && !mainWindow.isDestroyed()) {
      mainWindow.webContents.send('tcp-log', message);
    }
  } catch (error) {
    unifiedLog(`发送TCP日志失败: ${error.message}`, 'error');
  }
}

// 获取任务文件列表
ipcMain.handle('get-files-from-directory', async (event) => {
  try {
    const fs = require('fs');
    const path = require('path');
    
    // 获取存储任务数据的目录
    const taskDataDir = path.join(app.getPath('userData'), 'taskdata');
    
    // 检查目录是否存在
    if (!fs.existsSync(taskDataDir)) {
      fs.mkdirSync(taskDataDir, { recursive: true });
      unifiedLog(`创建任务数据目录: ${taskDataDir}`);
      return { success: true, files: [] };
    }
    
    // 读取目录中的文件
    const files = fs.readdirSync(taskDataDir);
    
    // 获取每个文件的详细信息
    const fileList = files.map(fileName => {
      const filePath = path.join(taskDataDir, fileName);
      const stats = fs.statSync(filePath);
      
      return {
        fileName,
        filePath,
        size: stats.size,
        mtime: stats.mtime.toISOString()
      };
    });
    return { success: true, files: fileList };
    
  } catch (error) {
    unifiedLog(`获取任务文件列表失败: ${JSON.stringify(error)}`, 'error');
    return { success: false, message: error.message };
  }
});



// 添加全局未捕获异常处理
process.on('uncaughtException', (error) => {
  unifiedLog(`未捕获的异常: ${JSON.stringify(error)}`, 'error');
  tcpBroadcastLog(`发生未捕获异常: ${error.message}`);
});

process.on('unhandledRejection', (reason, promise) => {
  logErrorToFile('未处理的Promise拒绝', reason);
  tcpBroadcastLog(`发生Promise拒绝: ${reason ? reason.message || String(reason) : '未知原因'}`);
});

// 当 Electron 完成初始化并准备创建浏览器窗口时调用此方法
app.whenReady().then(() => {
  createWindow();

  // 注册全局快捷键：F12 和 Ctrl+Shift+I 打开/关闭开发者工具（打包后同样可用）
  try {
    globalShortcut.register('F12', () => {
      const win = BrowserWindow.getFocusedWindow() || mainWindow;
      if (win) win.webContents.toggleDevTools();
    });
    globalShortcut.register('Control+Shift+I', () => {
      const win = BrowserWindow.getFocusedWindow() || mainWindow;
      if (win) win.webContents.toggleDevTools();
    });
  } catch (e) {
    unifiedLog(`注册快捷键失败: ${e.message}`);
  }

  // 启动WebSocket服务器
  setTimeout(() => {
    startWebSocketServer();
  }, 2000); // 延迟2秒启动，确保窗口已创建
  handleUdp(); // 初始化 UDP 处理
});

// 当所有窗口都关闭时退出应用
app.on("window-all-closed", () => {
  // 停止RTSP流服务器
  stopRTSPServer();
  // 停止WebSocket服务器
  stopWebSocketServer();
  if (udpSocket) {
    udpSocket.close();
  }
  if (process.platform !== "darwin") {
    app.quit();
  }
});

// 应用退出时清理资源
app.on("before-quit", () => {
  stopRTSPServer();
  stopWebSocketServer();
});

// 添加删除文件的IPC处理器
ipcMain.handle("delete-file", async (event, filePath) => {
  const fs = require("fs");
  const path = require("path");

  try {
    // 规范化路径
    let normalizedPath = filePath;

    // 处理相对路径，转换为绝对路径
    if (filePath.startsWith('./')) {
      // 获取应用根目录
      const appRoot = process.cwd();
      normalizedPath = path.resolve(appRoot, filePath);
    }

    unifiedLog(`准备删除路径: ${normalizedPath}`);
    unifiedLog(`原始路径: ${filePath}`);

    // 安全检查：确保路径在应用目录内
    const appRoot = process.cwd();
    if (!normalizedPath.startsWith(appRoot)) {
      return {
        success: false,
        error: "安全错误：不能删除应用目录外的文件",
        filePath: filePath,
        normalizedPath: normalizedPath
      };
    }

    // 检查文件或目录是否存在
    if (!fs.existsSync(normalizedPath)) {
      return {
        success: false,
        error: "文件或目录不存在",
        filePath: filePath,
        normalizedPath: normalizedPath
      };
    }

    const stats = fs.statSync(normalizedPath);

    if (stats.isDirectory()) {
      // 如果是目录，递归删除整个目录
      const deleteDirectory = (dirPath) => {
        if (fs.existsSync(dirPath)) {
          const files = fs.readdirSync(dirPath);
          unifiedLog(`目录 ${dirPath} 包含 ${files.length} 个文件/子目录`);

          files.forEach((file) => {
            const curPath = path.join(dirPath, file);
            const curStats = fs.lstatSync(curPath);

            if (curStats.isDirectory()) {
              unifiedLog(`删除子目录: ${curPath}`);
              deleteDirectory(curPath);
            } else {
              unifiedLog(`删除文件: ${curPath}`);
              fs.unlinkSync(curPath);
            }
          });

          unifiedLog(`删除空目录: ${dirPath}`);
          fs.rmdirSync(dirPath);
        }
      };

      deleteDirectory(normalizedPath);
      unifiedLog(`采集目录已完全删除: ${normalizedPath}`);
    } else {
      // 如果是文件，直接删除
      fs.unlinkSync(normalizedPath);
      unifiedLog(`文件已删除: ${normalizedPath}`);
    }

    return {
      success: true,
      filePath: filePath,
      normalizedPath: normalizedPath,
      message: stats.isDirectory() ? "采集目录删除成功" : "文件删除成功"
    };
  } catch (error) {
    unifiedLog("删除文件失败:", JSON.stringify(error), 'error');
    return {
      success: false,
      error: error.message,
      filePath: filePath,
      stack: error.stack
    };
  }
});

// 添加导出采集数据的IPC处理器
ipcMain.handle("export-collection-data", async (event, options) => {
  const fs = require("fs");
  const path = require("path");
  const archiver = require("archiver");

  try {
    unifiedLog("收到导出请求，参数:", JSON.stringify(options, null, 2));

    const { collectionData, exportFormat, isPackage } = options;

    unifiedLog("采集数据:", JSON.stringify(collectionData, null, 2));

    // 解析存储路径，获取时间戳
    const storagePath = collectionData.storagePath;
    if (!storagePath) {
      return {
        success: false,
        error: "存储路径信息缺失"
      };
    }

    // 提取时间戳，格式如: ./collections/20250731_113823
    const timestampMatch = storagePath.match(/\/collections\/(\d{8}_\d{6})/);
    if (!timestampMatch) {
      return {
        success: false,
        error: "无法从存储路径中提取时间戳"
      };
    }

    const timestamp = timestampMatch[1];
    const collectionDir = path.resolve(process.cwd(), storagePath);

    // 检查采集目录是否存在
    if (!fs.existsSync(collectionDir)) {
      return {
        success: false,
        error: `采集目录不存在: ${collectionDir}`
      };
    }

    // 读取采集目录中的文件
    const files = fs.readdirSync(collectionDir);
    unifiedLog(`采集目录中的文件: ${files.join(', ')}`);

    if (files.length === 0) {
      return {
        success: false,
        error: "采集目录为空"
      };
    }

    // 生成默认文件名 - 直接使用时间戳作为文件名
    const defaultFileName = timestamp; // 直接使用时间戳，如 20250815_104446

    // 根据导出格式选择文件扩展名
    let fileExtension = 'zip';
    let filters = [];

    if (exportFormat === 'zip') {
      fileExtension = 'zip';
      filters = [{ name: 'ZIP压缩包', extensions: ['zip'] }];
    } else if (exportFormat === 'dat') {
      fileExtension = 'dat';
      filters = [{ name: '自定义格式', extensions: ['dat'] }];
    } else {
      fileExtension = 'zip';
      filters = [{ name: 'ZIP压缩包', extensions: ['zip'] }];
    }

    // 使用系统文件保存对话框
    const saveResult = await dialog.showSaveDialog({
      title: '保存采集数据',
      defaultPath: `${defaultFileName}.${fileExtension}`,
      filters: filters
    });

    if (saveResult.canceled) {
      return {
        success: false,
        error: "用户取消了导出操作"
      };
    }

    const targetPath = saveResult.filePath;
    const targetDir = path.dirname(targetPath);

    // 创建导出目录
    if (!fs.existsSync(targetDir)) {
      fs.mkdirSync(targetDir, { recursive: true });
    }

    if (exportFormat === 'zip' || exportFormat === 'dat') {
      // 打包导出 - 创建ZIP文件
      return new Promise((resolve, reject) => {
        const output = fs.createWriteStream(targetPath);
        const archive = archiver('zip', {
          zlib: { level: 9 } // 设置压缩级别
        });

        output.on('close', () => {
          unifiedLog(`ZIP文件创建完成: ${targetPath}`);
          unifiedLog(`总大小: ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB`);

          resolve({
            success: true,
            exportPath: targetPath,
            fileSize: archive.pointer(),
            fileCount: files.length,
            message: `采集数据打包导出成功！\n文件: ${path.basename(targetPath)}\n大小: ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB\n包含文件: ${files.length} 个`
          });
        });

        archive.on('error', (err) => {
          unifiedLog('ZIP创建错误:', JSON.stringify(err), 'error');
          reject({
            success: false,
            error: `ZIP创建失败: ${err.message}`
          });
        });

        archive.pipe(output);

        // 将采集目录中的所有文件添加到ZIP中，保持原始文件名
        files.forEach(file => {
          const filePath = path.join(collectionDir, file);
          const stats = fs.statSync(filePath);

          if (stats.isFile()) {
            unifiedLog(`添加文件到ZIP: ${file}`);
            archive.file(filePath, { name: file }); // 直接添加文件，不创建子文件夹
          }
        });

        archive.finalize();
      });

    } else {
      // 单个文件导出（保持原有逻辑作为备用）
      const videoFiles = files.filter(file =>
        /\.(mp4|avi|mov|mkv|wmv|flv|webm|m4v)$/i.test(file)
      );
      const telemetryFile = files.find(file =>
        file === 'telemetry_data.json'
      );

      if (videoFiles.length === 0) {
        return {
          success: false,
          error: "未找到视频文件"
        };
      }

      const targetFileName = path.basename(targetPath, path.extname(targetPath));

      // 复制视频文件
      const videoExportResults = [];
      for (const videoFile of videoFiles) {
        const sourceVideoPath = path.join(collectionDir, videoFile);
        const targetVideoPath = path.join(targetDir, `${targetFileName}_${videoFile}`);

        try {
          fs.copyFileSync(sourceVideoPath, targetVideoPath);
          videoExportResults.push({
            originalFile: videoFile,
            exportedFile: path.basename(targetVideoPath),
            success: true
          });
        } catch (error) {
          videoExportResults.push({
            originalFile: videoFile,
            error: error.message,
            success: false
          });
        }
      }

      // 复制遥测数据文件
      let telemetryExportResult = null;
      if (telemetryFile) {
        const sourceTelemetryPath = path.join(collectionDir, telemetryFile);
        const targetTelemetryPath = path.join(targetDir, `${targetFileName}_telemetry.json`);

        try {
          fs.copyFileSync(sourceTelemetryPath, targetTelemetryPath);
          telemetryExportResult = {
            originalFile: telemetryFile,
            exportedFile: path.basename(targetTelemetryPath),
            success: true
          };
        } catch (error) {
          telemetryExportResult = {
            originalFile: telemetryFile,
            error: error.message,
            success: false
          };
        }
      }

      return {
        success: true,
        exportDir: targetDir,
        videoResults: videoExportResults,
        telemetryResult: telemetryExportResult,
        message: `采集数据导出成功！\n视频文件: ${videoExportResults.filter(r => r.success).length}/${videoFiles.length}\n遥测数据: ${telemetryExportResult && telemetryExportResult.success ? '已导出' : '未找到'}`
      };
    }

  } catch (error) {
    unifiedLog("导出采集数据失败:", JSON.stringify(error), 'error');
    return {
      success: false,
      error: error.message
    };
  }
});

// 添加批量导出数据的IPC处理器
ipcMain.handle("batch-export-data", async (event, options) => {
  const fs = require("fs");
  const path = require("path");
  const archiver = require("archiver");

  try {
      const { collectionDataList, exportFormat, isPackage } = options;

      if (!collectionDataList || collectionDataList.length === 0) {
        return {
          success: false,
          error: "没有要导出的数据"
        };
      }

      unifiedLog(`准备批量导出 ${collectionDataList.length} 个采集数据`);

    if (isPackage) {
      // 打包导出 - 将所有采集数据打包成一个ZIP文件
      const timestamp = new Date().toLocaleDateString('zh-CN', {
        timeZone: 'Asia/Shanghai',
        year: 'numeric',
        month: '2-digit',
        day: '2-digit'
      }).replace(/\//g, '');

      const defaultFileName = `批量采集数据_${timestamp}`;

      // 根据导出格式选择文件扩展名
      let fileExtension = 'zip';
      let filters = [];

      if (exportFormat === 'zip') {
        fileExtension = 'zip';
        filters = [{ name: 'ZIP压缩包', extensions: ['zip'] }];
      } else if (exportFormat === 'dat') {
        fileExtension = 'dat';
        filters = [{ name: '自定义格式', extensions: ['dat'] }];
      } else {
        fileExtension = 'zip';
        filters = [{ name: 'ZIP压缩包', extensions: ['zip'] }];
      }

      // 使用系统文件保存对话框
      const saveResult = await dialog.showSaveDialog({
        title: '保存批量采集数据',
        defaultPath: `${defaultFileName}.${fileExtension}`,
        filters: filters
      });

      if (saveResult.canceled) {
        return {
          success: false,
          error: "用户取消了批量导出操作"
        };
      }

      const targetPath = saveResult.filePath;
      const targetDir = path.dirname(targetPath);

      // 创建导出目录
      if (!fs.existsSync(targetDir)) {
        fs.mkdirSync(targetDir, { recursive: true });
      }

      // 创建ZIP文件
      return new Promise((resolve, reject) => {
        const output = fs.createWriteStream(targetPath);
        const archive = archiver('zip', {
          zlib: { level: 9 } // 设置压缩级别
        });

        let totalFiles = 0;
        let successfulCollections = 0;

        output.on('close', () => {
          unifiedLog(`批量ZIP文件创建完成: ${targetPath}`);
          unifiedLog(`总大小: ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB`);

          resolve({
            success: true,
            exportPath: targetPath,
            fileSize: archive.pointer(),
            totalCollections: collectionDataList.length,
            successfulCollections: successfulCollections,
            totalFiles: totalFiles,
            message: `批量采集数据打包导出成功！\n文件: ${path.basename(targetPath)}\n大小: ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB\n包含采集数据: ${successfulCollections}/${collectionDataList.length} 个\n总文件数: ${totalFiles} 个`
          });
        });

        archive.on('error', (err) => {
          unifiedLog('批量ZIP创建错误:', JSON.stringify(err), 'error');
          reject({
            success: false,
            error: `批量ZIP创建失败: ${err.message}`
          });
        });

        archive.pipe(output);

        // 逐个处理每个采集数据
        collectionDataList.forEach((collectionData, index) => {
          try {
            const storagePath = collectionData.storagePath;
            if (!storagePath) {
              unifiedLog(`跳过第 ${index + 1} 个数据：存储路径缺失`);
              return;
            }

            // 提取时间戳
            const timestampMatch = storagePath.match(/\/collections\/(\d{8}_\d{6})/);
            if (!timestampMatch) {
              unifiedLog(`跳过第 ${index + 1} 个数据：无法提取时间戳`);
              return;
            }

            const timestamp = timestampMatch[1];
            const collectionDir = path.resolve(process.cwd(), storagePath);

            // 检查采集目录是否存在
            if (!fs.existsSync(collectionDir)) {
              unifiedLog(`跳过第 ${index + 1} 个数据：目录不存在 ${collectionDir}`);
              return;
            }

            // 读取采集目录中的文件
            const files = fs.readdirSync(collectionDir);
            if (files.length === 0) {
              unifiedLog(`跳过第 ${index + 1} 个数据：目录为空`);
              return;
            }

            // 为每个采集数据创建一个子文件夹，使用时间戳作为文件夹名
            const collectionFolderName = timestamp; // 直接使用时间戳作为文件夹名

            // 将采集目录中的所有文件添加到ZIP中，保持原始文件名
            files.forEach(file => {
              const filePath = path.join(collectionDir, file);
              const stats = fs.statSync(filePath);

              if (stats.isFile()) {
                const zipEntryName = `${collectionFolderName}/${file}`; // 在ZIP中创建时间戳子文件夹
                unifiedLog(`添加文件到批量ZIP: ${zipEntryName}`);
                archive.file(filePath, { name: zipEntryName });
                totalFiles++;
              }
            });

            successfulCollections++;
            unifiedLog(`成功添加第 ${index + 1} 个采集数据: ${collectionFolderName}`);

          } catch (error) {
            unifiedLog(`处理第 ${index + 1} 个采集数据时出错:`, error.message, 'error');
          }
        });

        archive.finalize();
      });

    } else {
      // 逐个导出 - 让用户选择导出目录
      const result = await dialog.showOpenDialog({
        properties: ['openDirectory'],
        title: '选择批量导出目录'
      });

      if (result.canceled) {
        return {
          success: false,
          error: "用户取消了批量导出操作"
        };
      }

      const exportDir = result.filePaths[0];
      unifiedLog(`用户选择的批量导出目录: ${exportDir}`);

      const timestamp = new Date().toLocaleDateString('zh-CN', {
        timeZone: 'Asia/Shanghai',
        year: 'numeric',
        month: '2-digit',
        day: '2-digit'
      }).replace(/\//g, '');

      const exportResults = [];
      let successCount = 0;

      for (let i = 0; i < collectionDataList.length; i++) {
        const collectionData = collectionDataList[i];

        try {
          const storagePath = collectionData.storagePath;
          if (!storagePath) {
            exportResults.push({
              index: i + 1,
              success: false,
              error: "存储路径缺失"
            });
            continue;
          }

          // 提取时间戳
          const timestampMatch = storagePath.match(/\/collections\/(\d{8}_\d{6})/);
          if (!timestampMatch) {
            exportResults.push({
              index: i + 1,
              success: false,
              error: "无法提取时间戳"
            });
            continue;
          }

          const timestamp = timestampMatch[1];
          const collectionDir = path.resolve(process.cwd(), storagePath);

          // 检查采集目录是否存在
          if (!fs.existsSync(collectionDir)) {
            exportResults.push({
              index: i + 1,
              success: false,
              error: `目录不存在: ${collectionDir}`
            });
            continue;
          }

          // 读取采集目录中的文件
          const files = fs.readdirSync(collectionDir);
          if (files.length === 0) {
            exportResults.push({
              index: i + 1,
              success: false,
              error: "目录为空"
            });
            continue;
          }

          // 为每个采集数据创建一个子文件夹
          const collectionFolderName = `采集数据_${timestamp}_${i + 1}`;
          const collectionExportDir = path.join(exportDir, collectionFolderName);

          // 创建子文件夹
          if (!fs.existsSync(collectionExportDir)) {
            fs.mkdirSync(collectionExportDir, { recursive: true });
          }

          // 复制所有文件
          const copiedFiles = [];
          files.forEach(file => {
            const sourcePath = path.join(collectionDir, file);
            const targetPath = path.join(collectionExportDir, file);

            try {
                fs.copyFileSync(sourcePath, targetPath);
                copiedFiles.push(file);
              } catch (error) {
                unifiedLog(`复制文件失败: ${file}`, error.message, 'error');
              }
          });

          exportResults.push({
            index: i + 1,
            fileName: collectionFolderName,
            path: collectionExportDir,
            copiedFiles: copiedFiles,
            success: true
          });

          successCount++;

        } catch (error) {
          exportResults.push({
            index: i + 1,
            success: false,
            error: error.message
          });
        }
      }

      unifiedLog(`批量导出完成，成功导出 ${successCount}/${collectionDataList.length} 个采集数据到: ${exportDir}`);

      return {
        success: true,
        exportDir: exportDir,
        exportResults: exportResults,
        totalCount: collectionDataList.length,
        successCount: successCount,
        message: `批量导出完成，成功导出 ${successCount}/${collectionDataList.length} 个采集数据到: ${exportDir}`
      };
    }

  } catch (error) {
    unifiedLog("批量导出数据失败:", JSON.stringify(error), 'error');
    return {
      success: false,
      error: error.message
    };
  }
});

// 辅助函数：转换为CSV格式
function convertToCSV(data) {
  if (!Array.isArray(data) || data.length === 0) {
    return '';
  }

  const headers = Object.keys(data[0]);
  const csvRows = [headers.join(',')];

  for (const row of data) {
    const values = headers.map(header => {
      const value = row[header];
      // 处理包含逗号、引号或换行符的值
      if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) {
        return `"${value.replace(/"/g, '""')}"`;
      }
      return value || '';
    });
    csvRows.push(values.join(','));
  }

  return csvRows.join('\n');
}

// 添加获取采集文件的IPC处理器
ipcMain.handle("get-collection-files", async (event, options) => {
  const fs = require("fs");
  const path = require("path");

  try {
    const { storagePath } = options;

    if (!storagePath) {
      return {
        success: false,
        error: "存储路径为空"
      };
    }

    // 解析存储路径，获取时间戳
    // const timestampMatch = storagePath.match(/\/collections\/(\d{8}\d{6})/);
    // if (!timestampMatch) {
    //   return {
    //     success: false,
    //     error: "无法从存储路径中提取时间戳"
    //   };
    // }

    // 处理路径中的空格和特殊字符
    let normalizedStoragePath = storagePath;
    if (storagePath.startsWith('./')) {
      normalizedStoragePath = storagePath.substring(2); // 移除 './'
    }

    const collectionDir = path.resolve(process.cwd(), normalizedStoragePath);

    // 检查采集目录是否存在
    if (!fs.existsSync(collectionDir)) {
      return {
        success: false,
        error: `采集目录不存在: ${collectionDir}`
      };
    }

    // 读取采集目录中的文件
    const files = fs.readdirSync(collectionDir);
    const videoFiles = files.filter(file =>
      /\.(mp4|avi|mov|mkv|wmv|flv|webm|m4v)$/i.test(file)
    ).map(file => ({
      fileName: file,
      localPath: path.join(collectionDir, file),
      size: fs.statSync(path.join(collectionDir, file)).size
    }));

    const telemetryFile = files.find(file =>
      file === 'telemetry_data.json'
    );

    const telemetryInfo = telemetryFile ? {
      fileName: telemetryFile,
      localPath: path.join(collectionDir, telemetryFile),
      size: fs.statSync(path.join(collectionDir, telemetryFile)).size
    } : null;

    unifiedLog(`获取采集文件成功: ${collectionDir}`);
    unifiedLog(`视频文件: ${videoFiles.length} 个`);
    unifiedLog(`遥测文件: ${telemetryInfo ? '存在' : '不存在'}`);

    return {
      success: true,
      files: {
        videos: videoFiles,
        telemetry: telemetryInfo,
        collectionDir: collectionDir
      }
    };

  } catch (error) {
    unifiedLog("获取采集文件失败:", JSON.stringify(error), 'error');
    return {
      success: false,
      error: error.message
    };
  }
});

// 修改为读取文件夹下的所有txt文件，支持本地路径和服务端路径
ipcMain.handle("read-telemetry-file", async (event, options) => {
  try {
    const filePath = options.filePath;
    
    if (!filePath) {
      return {
        success: false,
        error: "文件路径不能为空"
      };
    }
    
    // 直接使用提供的完整URL发起请求
      try {
        // 对URL进行编码，处理中文等特殊字符
        const encodedFilePath = encodeURI(filePath);
      const result = await axios.get(encodedFilePath, {
          responseType: 'text'
        });
        unifiedLog(`文件${filePath}内容为:`, result.data);
      // 直接返回文件内容
      return {
        success: true,
        content: result.data
      };
    } catch (error) {
        unifiedLog("文件请求失败:", JSON.stringify(error), 'error');
        return {
          success: false,
          error: `文件${filePath}请求失败: ${error.message || "未知错误"}`
        };
      };
  } catch (error) {
    unifiedLog("读取文件夹失败:", JSON.stringify(error), 'error');
    return {
      success: false,
      error: error.message
    };
  }
});

app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

app.on("will-quit", () => {
  try {
    globalShortcut.unregisterAll();
  } catch (e) {
    unifiedLog('注销快捷键失败:', e.message);
  }
  if (udpSocket) {
    udpSocket.close();
    udpSocket = null;
  }
});

// 错误处理
process.on("uncaughtException", (error) => {
  logErrorToFile("未捕获的异常", error);
});
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";

// main.js - 完整的下载处理程序
ipcMain.handle('download-video', async (event, options) => {
  try {
    const { url, defaultFilename } = options;

    if (!url || !defaultFilename) {
      return {
        success: false,
        message: '缺少必要参数: url和defaultFilename都是必需的'
      };
    }

    // 显示保存对话框
    const { filePath, canceled } = await dialog.showSaveDialog({
      title: '保存视频文件',
      defaultPath: defaultFilename,
      filters: [{ name: '视频文件', extensions: ['mp4'] }]
    });

    if (canceled || !filePath) {
      return { success: false, message: '用户取消了保存' };
    }

    // 使用fs.createWriteStream创建文件流（需要fs模块）
    await new Promise((resolve, reject) => {
      // ✅ 使用fs模块创建写入流
      const file = fs.createWriteStream(filePath);
      const protocol = url.startsWith('https') ? https : http;

      protocol.get(url, (response) => {
        if (response.statusCode !== 200) {
          reject(new Error(`请求失败，状态码: ${response.statusCode}`));
          return;
        }

        response.pipe(file);

        file.on('finish', () => {
          file.close(resolve); //  完成后关闭文件流
        });
      }).on('error', (err) => {
        // ✅ 发生错误时使用fs.unlink删除文件
        fs.unlink(filePath, () => reject(err));
      });
    });

    return { success: true, path: filePath };
  } catch (error) {
    unifiedLog('下载失败:', JSON.stringify(error), 'error');
    return {
      success: false,
      message: `下载失败: ${error.message}`
    };
  }
});
ipcMain.handle('upload-video-file', async (event, { file, targetPath, signal }) => {
  return new Promise((resolve, reject) => {
    // 1. 安全校验：防止路径穿越
    const baseDir = path.resolve(app.getAppPath(), 'collections');
    const fullTargetPath = path.resolve(baseDir, `./${currentRow.dataId}/videos/`); // 与渲染进程路径对齐
    if (!safePathCheck(fullTargetPath, baseDir)) {
      return reject(new Error('上传路径非法，禁止跨目录操作'));
    }

    // 2. 创建目标目录（若不存在）
    if (!fs.existsSync(fullTargetPath)) {
      fs.mkdirSync(fullTargetPath, { recursive: true });
    }

    // 3. 生成目标文件路径（保留原文件名，避免重复）
    const fileName = `${Date.now()}-${path.basename(file.name)}`; // 加时间戳防重复
    const savePath = path.resolve(fullTargetPath, fileName);

    // 4. 创建文件写入流
    const writeStream = fs.createWriteStream(savePath);
    let uploadedBytes = 0;
    const totalBytes = file.size;

    // 5. 监听文件读取进度（需渲染进程配合传递分块数据，此处简化为模拟进度）
    // 实际场景：渲染进程需将File对象分块（如1MB/块）通过IPC发送，主进程接收后写入
    // 此处为简化示例，假设通过blob分块传输，主进程接收分块并累计进度
    event.sender.on('video-chunk', (_, chunk) => {
      if (signal.aborted) {
        writeStream.destroy();
        return reject(new Error('upload-aborted'));
      }
      writeStream.write(chunk);
      uploadedBytes += chunk.length;
      // 向渲染进程发送进度更新
      event.sender.send('upload-progress', uploadedBytes / totalBytes);
    });

    // 6. 监听写入完成
    writeStream.on('finish', () => {
      resolve({
        success: true,
        savePath: savePath,
        fileName: fileName,
        fileSize: totalBytes
      });
    });

    // 7. 监听写入错误
    writeStream.on('error', (err) => {
      // 错误时删除不完整文件
      if (fs.existsSync(savePath)) {
        fs.unlinkSync(savePath);
      }
      reject(new Error(`文件写入失败：${err.message}`));
    });

    // 8. 监听上传取消（中断信号）
    signal.addEventListener('abort', () => {
      writeStream.destroy();
      if (fs.existsSync(savePath)) {
        fs.unlinkSync(savePath);
      }
      reject(new Error('upload-aborted'));
    });
  });
});
ipcMain.handle('show-save-dialog', async (event, options) => {
  try {
    // 显示系统保存对话框，让用户选择路径
    const result = await dialog.showSaveDialog(mainWindow, options);
    return result; // 返回 { canceled: boolean, filePath: string }
  } catch (error) {
    unifiedLog('保存对话框调用失败:', JSON.stringify(error), 'error');
    return { canceled: true, error: error.message };
  }
});

// 2. 注册「写入文件到指定路径」IPC 接口（主进程处理文件更安全）
ipcMain.handle('write-file-to-path', async (event, { buffer, filePath }) => {
  try {
    // 确保目标目录存在（不存在则创建）
    const dirPath = path.dirname(filePath);
    if (!fs.existsSync(dirPath)) {
      fs.mkdirSync(dirPath, { recursive: true }); // recursive: 递归创建多级目录
    }

    // 写入文件（Buffer 格式）
    fs.writeFileSync(filePath, buffer);
    return { success: true, message: '文件保存成功' };
  } catch (error) {
    unifiedLog('文件写入失败:', JSON.stringify(error), 'error');
    return {
      success: false,
      message: `文件保存失败: ${error.message}`,
      error: error.stack
    };
  }
});

// 3. 注册「打开文件所在文件夹」接口（可选，优化用户体验）
ipcMain.on('open-file-folder', (event, filePath) => {
  try {
    // 检查文件是否存在
    if (fs.existsSync(filePath)) {
      mainWindow.webContents.send('open-folder-success', '正在打开文件夹...');
      // 用系统默认方式打开文件夹并选中文件
      shell.showItemInFolder(filePath);
    } else {
      mainWindow.webContents.send('open-folder-error', '文件不存在，无法打开文件夹');
    }
  } catch (error) {
    mainWindow.webContents.send('open-folder-error', `打开文件夹失败: ${error.message}`);
  }
});
// 1. 监听渲染进程的“请求选择保存路径”事件
ipcMain.handle('request-save-path', async (event, defaultFileName) => {
  try {
    // 打开“保存文件”对话框，让用户选择路径
    const { filePath, canceled } = await dialog.showSaveDialog(mainWindow, {
      title: '选择压缩包保存位置',
      defaultPath: path.join(app.getPath('downloads'), defaultFileName), // 默认路径：下载文件夹
      filters: [
        { name: 'ZIP 压缩包', extensions: ['zip'] }, // 仅允许选择 zip 格式
        { name: '所有文件', extensions: ['*'] }
      ]
    });

    if (canceled) return { canceled: true }; // 用户取消选择
    return { canceled: false, filePath }; // 返回用户选择的路径
  } catch (error) {
    unifiedLog('选择保存路径失败：', JSON.stringify(error), 'error');
    return { canceled: true, error: error.message };
  }
});

// 2. 监听渲染进程的“下载文件”事件
ipcMain.handle('download-file', async (event, { fileUrl, savePath }) => {
  try {
    // 发起 GET 请求下载文件（需处理跨域，若接口有授权需带 headers）
    const response = await axios({
      url: fileUrl,
      method: 'GET',
      responseType: 'stream', // 以流的形式接收文件（避免内存占用过高）
      // 若接口需要授权（如 Token），添加 headers：
      // headers: {
      //   'Authorization': `Bearer ${yourToken}`
      // }
    });

    // 创建可写流，将文件写入用户选择的路径
    const writeStream = fs.createWriteStream(savePath);
    response.data.pipe(writeStream);

    // 等待文件写入完成
    await new Promise((resolve, reject) => {
      writeStream.on('finish', resolve);
      writeStream.on('error', (err) => {
        fs.unlinkSync(savePath); // 写入失败时删除空文件
        reject(err);
      });
    });

    return { success: true, message: '下载完成' };
  } catch (error) {
    unifiedLog('文件下载失败：', JSON.stringify(error), 'error');
    return { success: false, message: `下载失败：${error.message}` };
  }
});
ipcMain.handle('open-file-dialog', async (event, options) => {
  try {
    // 调用 Electron 的文件选择对话框
    const { filePaths, canceled } = await dialog.showOpenDialog(mainWindow, {
      title: options.title || '选择文件',
      defaultPath: app.getPath('downloads'), // 默认打开“下载”文件夹
      filters: options.filters || [], // 文件类型过滤
      properties: ['openFile'] // 仅允许选择单个文件
    });

    if (canceled) return { canceled: true };
    // 返回选中的文件路径（取第一个，因仅允许选单个文件）
    const selectedFile = filePaths[0];
    // 获取文件基本信息（大小、名称等，用于渲染进程显示）
    const fileStats = fs.statSync(selectedFile);
    return {
      canceled: false,
      file: {
        path: selectedFile,
        name: path.basename(selectedFile),
        size: fileStats.size, // 字节数
        ext: path.extname(selectedFile).toLowerCase() // 文件后缀
      }
    };
  } catch (error) {
    unifiedLog('打开文件对话框失败：', JSON.stringify(error), 'error');
    return { canceled: true, error: error.message };
  }
});
// 处理文件选择对话框
ipcMain.handle('dialog:openFile', async (event, options) => {
  return dialog.showOpenDialog(options);
});

// 处理文件读取（主进程安全读取文件）
ipcMain.handle('file:readBinary', async (event, filePath) => {
  try {
    // 主进程拥有完整的Node.js权限，可安全使用fs
    const buffer = fs.readFileSync(filePath);
    // 转换为ArrayBuffer并返回（渲染进程可识别）
    return {
      success: true,
      data: buffer.buffer // 返回ArrayBuffer
    };
  } catch (error) {
    return {
      success: false,
      error: error.message
    };
  }
});
ipcMain.handle('downloadFileWithProgress', async (event, { fileUrl, savePath }) => {
  return new Promise((resolve, reject) => {
    // 根据URL协议选择使用http或https模块
    const protocol = fileUrl.startsWith('https') ? https : http;

    // 使用选定的协议模块发送请求
    protocol.get(fileUrl, (response) => {
      // 检查响应状态
      if (response.statusCode !== 200) {
        reject(new Error(`请求失败，状态码: ${response.statusCode}`));
        response.resume();
        return;
      }

      // 获取文件总大小
      const totalSize = parseInt(response.headers['content-length'], 10);
      let transferredSize = 0;

      // 创建文件写入流
      const fileStream = fs.createWriteStream(savePath);

      // 监听数据传输进度
      response.on('data', (chunk) => {
        transferredSize += chunk.length;
        // 计算进度百分比
        const progress = {
          percent: transferredSize / totalSize,
          transferred: formatFileSize(transferredSize),
          total: formatFileSize(totalSize)
        };
        // 向渲染进程发送进度更新
        mainWindow.webContents.send('downloadProgress', progress);
        fileStream.write(chunk);
      });

      // 完成下载
      response.on('end', () => {
        fileStream.end();
        resolve({ success: true, message: '下载完成' });
      });

      // 错误处理
      fileStream.on('error', (err) => {
        fs.unlink(savePath, () => {}); // 尝试删除不完整文件
        reject(new Error(`文件写入错误: ${err.message}`));
      });

    }).on('error', (err) => {
      reject(new Error(`请求错误: ${err.message}`));
    });
  });
});
// 辅助函数：格式化文件大小
function formatFileSize(bytes) {
  if (bytes === 0) return '0 Bytes';
  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// 初始化日志系统
initLogFile();
